@rarebit-one/harness-kernel 0.5.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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +286 -0
  3. package/dist/agent.d.ts +71 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +258 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/connectors/mcpClient.d.ts +22 -0
  8. package/dist/connectors/mcpClient.d.ts.map +1 -0
  9. package/dist/connectors/mcpClient.js +63 -0
  10. package/dist/connectors/mcpClient.js.map +1 -0
  11. package/dist/context/types.d.ts +68 -0
  12. package/dist/context/types.d.ts.map +1 -0
  13. package/dist/context/types.js +43 -0
  14. package/dist/context/types.js.map +1 -0
  15. package/dist/engines/capability.d.ts +67 -0
  16. package/dist/engines/capability.d.ts.map +1 -0
  17. package/dist/engines/capability.js +73 -0
  18. package/dist/engines/capability.js.map +1 -0
  19. package/dist/engines/capabilityEmissions.d.ts +27 -0
  20. package/dist/engines/capabilityEmissions.d.ts.map +1 -0
  21. package/dist/engines/capabilityEmissions.js +37 -0
  22. package/dist/engines/capabilityEmissions.js.map +1 -0
  23. package/dist/engines/capabilityMcp.d.ts +23 -0
  24. package/dist/engines/capabilityMcp.d.ts.map +1 -0
  25. package/dist/engines/capabilityMcp.js +36 -0
  26. package/dist/engines/capabilityMcp.js.map +1 -0
  27. package/dist/engines/capabilityStdio.d.ts +46 -0
  28. package/dist/engines/capabilityStdio.d.ts.map +1 -0
  29. package/dist/engines/capabilityStdio.js +39 -0
  30. package/dist/engines/capabilityStdio.js.map +1 -0
  31. package/dist/engines/claudeCode.d.ts +73 -0
  32. package/dist/engines/claudeCode.d.ts.map +1 -0
  33. package/dist/engines/claudeCode.js +100 -0
  34. package/dist/engines/claudeCode.js.map +1 -0
  35. package/dist/engines/claudeCodeDriver.d.ts +14 -0
  36. package/dist/engines/claudeCodeDriver.d.ts.map +1 -0
  37. package/dist/engines/claudeCodeDriver.js +90 -0
  38. package/dist/engines/claudeCodeDriver.js.map +1 -0
  39. package/dist/engines/codex.d.ts +72 -0
  40. package/dist/engines/codex.d.ts.map +1 -0
  41. package/dist/engines/codex.js +157 -0
  42. package/dist/engines/codex.js.map +1 -0
  43. package/dist/engines/codexDriver.d.ts +43 -0
  44. package/dist/engines/codexDriver.d.ts.map +1 -0
  45. package/dist/engines/codexDriver.js +110 -0
  46. package/dist/engines/codexDriver.js.map +1 -0
  47. package/dist/engines/connectorMcp.d.ts +60 -0
  48. package/dist/engines/connectorMcp.d.ts.map +1 -0
  49. package/dist/engines/connectorMcp.js +137 -0
  50. package/dist/engines/connectorMcp.js.map +1 -0
  51. package/dist/engines/index.d.ts +33 -0
  52. package/dist/engines/index.d.ts.map +1 -0
  53. package/dist/engines/index.js +29 -0
  54. package/dist/engines/index.js.map +1 -0
  55. package/dist/engines/native.d.ts +55 -0
  56. package/dist/engines/native.d.ts.map +1 -0
  57. package/dist/engines/native.js +117 -0
  58. package/dist/engines/native.js.map +1 -0
  59. package/dist/engines/types.d.ts +107 -0
  60. package/dist/engines/types.d.ts.map +1 -0
  61. package/dist/engines/types.js +2 -0
  62. package/dist/engines/types.js.map +1 -0
  63. package/dist/events.d.ts +211 -0
  64. package/dist/events.d.ts.map +1 -0
  65. package/dist/events.js +69 -0
  66. package/dist/events.js.map +1 -0
  67. package/dist/index.d.ts +63 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +82 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/loop.d.ts +132 -0
  72. package/dist/loop.d.ts.map +1 -0
  73. package/dist/loop.js +77 -0
  74. package/dist/loop.js.map +1 -0
  75. package/dist/models/chat.d.ts +65 -0
  76. package/dist/models/chat.d.ts.map +1 -0
  77. package/dist/models/chat.js +66 -0
  78. package/dist/models/chat.js.map +1 -0
  79. package/dist/models/middleware.d.ts +63 -0
  80. package/dist/models/middleware.d.ts.map +1 -0
  81. package/dist/models/middleware.js +139 -0
  82. package/dist/models/middleware.js.map +1 -0
  83. package/dist/models/registry.d.ts +78 -0
  84. package/dist/models/registry.d.ts.map +1 -0
  85. package/dist/models/registry.js +0 -0
  86. package/dist/models/registry.js.map +1 -0
  87. package/dist/models/types.d.ts +149 -0
  88. package/dist/models/types.d.ts.map +1 -0
  89. package/dist/models/types.js +64 -0
  90. package/dist/models/types.js.map +1 -0
  91. package/dist/primitives/codeExec.d.ts +25 -0
  92. package/dist/primitives/codeExec.d.ts.map +1 -0
  93. package/dist/primitives/codeExec.js +69 -0
  94. package/dist/primitives/codeExec.js.map +1 -0
  95. package/dist/primitives/download.d.ts +19 -0
  96. package/dist/primitives/download.d.ts.map +1 -0
  97. package/dist/primitives/download.js +113 -0
  98. package/dist/primitives/download.js.map +1 -0
  99. package/dist/primitives/fs.d.ts +5 -0
  100. package/dist/primitives/fs.d.ts.map +1 -0
  101. package/dist/primitives/fs.js +59 -0
  102. package/dist/primitives/fs.js.map +1 -0
  103. package/dist/primitives/http.d.ts +100 -0
  104. package/dist/primitives/http.d.ts.map +1 -0
  105. package/dist/primitives/http.js +257 -0
  106. package/dist/primitives/http.js.map +1 -0
  107. package/dist/providers/anthropic.d.ts +15 -0
  108. package/dist/providers/anthropic.d.ts.map +1 -0
  109. package/dist/providers/anthropic.js +90 -0
  110. package/dist/providers/anthropic.js.map +1 -0
  111. package/dist/providers/clientOptions.d.ts +14 -0
  112. package/dist/providers/clientOptions.d.ts.map +1 -0
  113. package/dist/providers/clientOptions.js +26 -0
  114. package/dist/providers/clientOptions.js.map +1 -0
  115. package/dist/providers/index.d.ts +24 -0
  116. package/dist/providers/index.d.ts.map +1 -0
  117. package/dist/providers/index.js +51 -0
  118. package/dist/providers/index.js.map +1 -0
  119. package/dist/providers/mock.d.ts +25 -0
  120. package/dist/providers/mock.d.ts.map +1 -0
  121. package/dist/providers/mock.js +85 -0
  122. package/dist/providers/mock.js.map +1 -0
  123. package/dist/providers/openai.d.ts +32 -0
  124. package/dist/providers/openai.d.ts.map +1 -0
  125. package/dist/providers/openai.js +118 -0
  126. package/dist/providers/openai.js.map +1 -0
  127. package/dist/providers/openrouter.d.ts +29 -0
  128. package/dist/providers/openrouter.d.ts.map +1 -0
  129. package/dist/providers/openrouter.js +35 -0
  130. package/dist/providers/openrouter.js.map +1 -0
  131. package/dist/providers/types.d.ts +60 -0
  132. package/dist/providers/types.d.ts.map +1 -0
  133. package/dist/providers/types.js +2 -0
  134. package/dist/providers/types.js.map +1 -0
  135. package/dist/routing/staticResolver.d.ts +41 -0
  136. package/dist/routing/staticResolver.d.ts.map +1 -0
  137. package/dist/routing/staticResolver.js +67 -0
  138. package/dist/routing/staticResolver.js.map +1 -0
  139. package/dist/routing/types.d.ts +72 -0
  140. package/dist/routing/types.d.ts.map +1 -0
  141. package/dist/routing/types.js +11 -0
  142. package/dist/routing/types.js.map +1 -0
  143. package/dist/secrets.d.ts +8 -0
  144. package/dist/secrets.d.ts.map +1 -0
  145. package/dist/secrets.js +14 -0
  146. package/dist/secrets.js.map +1 -0
  147. package/dist/tools/metadata.d.ts +93 -0
  148. package/dist/tools/metadata.d.ts.map +1 -0
  149. package/dist/tools/metadata.js +49 -0
  150. package/dist/tools/metadata.js.map +1 -0
  151. package/dist/tools/modelTool.d.ts +35 -0
  152. package/dist/tools/modelTool.d.ts.map +1 -0
  153. package/dist/tools/modelTool.js +37 -0
  154. package/dist/tools/modelTool.js.map +1 -0
  155. package/dist/tools/registry.d.ts +65 -0
  156. package/dist/tools/registry.d.ts.map +1 -0
  157. package/dist/tools/registry.js +166 -0
  158. package/dist/tools/registry.js.map +1 -0
  159. package/dist/types.d.ts +71 -0
  160. package/dist/types.d.ts.map +1 -0
  161. package/dist/types.js +10 -0
  162. package/dist/types.js.map +1 -0
  163. package/package.json +72 -0
package/dist/loop.d.ts ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Extension point 7 — the control loop itself.
3
+ *
4
+ * Every other seam in this kernel answers "which implementation?" for something
5
+ * the loop *uses*: a model, a route, a context source, a tool. The loop that
6
+ * ties them together was the one piece an application could not replace, so
7
+ * anything that needed different control flow — a plan-then-execute shape, a
8
+ * loop that pauses on `ToolMetadata.requiresConfirmation`, a loop that fans a
9
+ * step out to subagents — had to fork {@link runAgent} and inherit every future
10
+ * fix by hand.
11
+ *
12
+ * That gap is not hypothetical: `ToolMetadata` already declares
13
+ * `requiresConfirmation`, and `toolsRequiringConfirmation()` already selects
14
+ * for it, but a confirmation flow has to interpose between "the model asked for
15
+ * this tool" and "the tool ran" — the exact seam that did not exist. The
16
+ * metadata described a control-flow decision the kernel gave no way to make.
17
+ *
18
+ * The kernel still ships exactly one loop, `nativeLoop`, and it is the same
19
+ * code that has always run. This adds the interface around it, not an
20
+ * alternative to it.
21
+ */
22
+ import type { LoopEventEmitter, RunEventEmitter, RunOutcome } from "./events.js";
23
+ import type { ChatModel } from "./models/chat.js";
24
+ import type { Tool } from "./tools/registry.js";
25
+ /**
26
+ * One run's materials, resolved.
27
+ *
28
+ * Everything here is already decided: the model is bound (registry-resolved and
29
+ * middleware-wrapped if it was going to be), the prompts are built, the tool
30
+ * surface is projected, the budgets are numbers rather than optionals. A loop
31
+ * decides *control flow* and nothing else — which is what keeps a second
32
+ * implementation small enough to be worth writing.
33
+ */
34
+ export interface LoopRequest {
35
+ model: ChatModel;
36
+ system: string;
37
+ userPrompt: string;
38
+ tools: Tool[];
39
+ limits: {
40
+ maxSteps: number;
41
+ maxDurationMs: number;
42
+ };
43
+ }
44
+ /**
45
+ * What a loop is given to talk to the outside world. Both are the same channels
46
+ * the rest of the kernel uses, so a loop needs no privileged access.
47
+ *
48
+ * `events` is an **emitter, not a raw sink**, and that distinction is the whole
49
+ * point: `seq` and `at` are the emitter's to stamp, so a loop physically cannot
50
+ * emit an unnumbered event and quietly break the gapless-sequence guarantee that
51
+ * makes a dropped event detectable. It is required rather than optional because
52
+ * `runEventEmitter(undefined)` is a working no-op — a loop never has to branch
53
+ * on whether anyone is listening.
54
+ *
55
+ * A loop emits only what happens *inside* it — turns, tool calls, budgets. The
56
+ * bookends are not its job, and the emitter's type says so: `LoopEventEmitter`
57
+ * excludes `run.started` and `run.finished`, so a loop cannot emit a duplicate
58
+ * start or a premature terminal event even by accident. See
59
+ * {@link runWithEvents}.
60
+ */
61
+ export interface LoopContext {
62
+ log: (line: string) => void;
63
+ events: LoopEventEmitter;
64
+ }
65
+ /**
66
+ * What {@link runWithEvents} is given: the same shape, but with the unrestricted
67
+ * emitter, because the bookends are its job and nobody else's.
68
+ */
69
+ export interface RunContext {
70
+ log: (line: string) => void;
71
+ events: RunEventEmitter;
72
+ }
73
+ /**
74
+ * What a loop returns.
75
+ *
76
+ * Richer than {@link runAgent}'s bare string on purpose: `outcome` and `steps`
77
+ * are facts the loop alone knows, and a caller that had to infer "did this
78
+ * finish or did it run out of budget?" from the prose would be guessing. The
79
+ * same two values appear on the `run.finished` event, and they must agree —
80
+ * the event stream and the return value are two views of one run, never two
81
+ * sources of truth.
82
+ */
83
+ export interface LoopResult {
84
+ text: string;
85
+ steps: number;
86
+ /**
87
+ * Why the run stopped. The native loop never *returns* `"failed"` — that path
88
+ * throws, and the terminal event is emitted before the error propagates. The
89
+ * value is in the union because a custom loop may legitimately choose to
90
+ * report a failure rather than throw one.
91
+ */
92
+ outcome: RunOutcome;
93
+ }
94
+ /**
95
+ * A pluggable control loop. Implementations: the native tool-use loop, or an
96
+ * application's own.
97
+ *
98
+ * `name` exists for the same reason `AgentEngine.name` does — so a log line or
99
+ * an event can say which one ran. There is deliberately no `supports()`: a loop
100
+ * is handed materials that are already resolved, so there is no capability for
101
+ * it to refuse. An engine still refuses through its own `supports()`.
102
+ */
103
+ export interface Loop {
104
+ readonly name: string;
105
+ run(req: LoopRequest, ctx: LoopContext): Promise<LoopResult>;
106
+ }
107
+ /**
108
+ * The kernel's one loop: the provider-neutral tool-use loop that `runAgent` has
109
+ * always run, behind the interface.
110
+ *
111
+ * This is the same code path, not a reimplementation — `runAgent` and
112
+ * `nativeLoop` both delegate to it, so there is no second behaviour to keep in
113
+ * sync and no way for the two to drift.
114
+ */
115
+ export declare const nativeLoop: Loop;
116
+ /**
117
+ * Run a loop with the run's bookend events emitted around it.
118
+ *
119
+ * The bookends belong HERE rather than inside any loop, because a loop that
120
+ * forgets them produces a run that appears never to have started — and after
121
+ * the loop became a seam, "any loop" includes ones this kernel has never seen.
122
+ * A guarantee that depends on every future implementor remembering it is not a
123
+ * guarantee. Both entry points — `runAgent` and `NativeEngine` — come through
124
+ * here, so the property holds no matter whose loop runs.
125
+ *
126
+ * On a throw it closes the stream with `outcome: "failed"` and then **rethrows
127
+ * the original error, untouched**. This changes what is observed, never what is
128
+ * thrown. `steps` and `text` are omitted on that path: the loop never returned
129
+ * a result, so there is no honest value for them.
130
+ */
131
+ export declare function runWithEvents(loop: Loop, req: LoopRequest, ctx: RunContext): Promise<LoopResult>;
132
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;CACpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,MAAM,EAAE,eAAe,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,OAAO,EAAE,UAAU,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;CAC7D;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,IAGxB,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,UAAU,CAAC,CA2BrB"}
package/dist/loop.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Extension point 7 — the control loop itself.
3
+ *
4
+ * Every other seam in this kernel answers "which implementation?" for something
5
+ * the loop *uses*: a model, a route, a context source, a tool. The loop that
6
+ * ties them together was the one piece an application could not replace, so
7
+ * anything that needed different control flow — a plan-then-execute shape, a
8
+ * loop that pauses on `ToolMetadata.requiresConfirmation`, a loop that fans a
9
+ * step out to subagents — had to fork {@link runAgent} and inherit every future
10
+ * fix by hand.
11
+ *
12
+ * That gap is not hypothetical: `ToolMetadata` already declares
13
+ * `requiresConfirmation`, and `toolsRequiringConfirmation()` already selects
14
+ * for it, but a confirmation flow has to interpose between "the model asked for
15
+ * this tool" and "the tool ran" — the exact seam that did not exist. The
16
+ * metadata described a control-flow decision the kernel gave no way to make.
17
+ *
18
+ * The kernel still ships exactly one loop, `nativeLoop`, and it is the same
19
+ * code that has always run. This adds the interface around it, not an
20
+ * alternative to it.
21
+ */
22
+ import { runNativeLoop } from "./agent.js";
23
+ /**
24
+ * The kernel's one loop: the provider-neutral tool-use loop that `runAgent` has
25
+ * always run, behind the interface.
26
+ *
27
+ * This is the same code path, not a reimplementation — `runAgent` and
28
+ * `nativeLoop` both delegate to it, so there is no second behaviour to keep in
29
+ * sync and no way for the two to drift.
30
+ */
31
+ export const nativeLoop = {
32
+ name: "native",
33
+ run: (req, ctx) => runNativeLoop(req, ctx),
34
+ };
35
+ /**
36
+ * Run a loop with the run's bookend events emitted around it.
37
+ *
38
+ * The bookends belong HERE rather than inside any loop, because a loop that
39
+ * forgets them produces a run that appears never to have started — and after
40
+ * the loop became a seam, "any loop" includes ones this kernel has never seen.
41
+ * A guarantee that depends on every future implementor remembering it is not a
42
+ * guarantee. Both entry points — `runAgent` and `NativeEngine` — come through
43
+ * here, so the property holds no matter whose loop runs.
44
+ *
45
+ * On a throw it closes the stream with `outcome: "failed"` and then **rethrows
46
+ * the original error, untouched**. This changes what is observed, never what is
47
+ * thrown. `steps` and `text` are omitted on that path: the loop never returned
48
+ * a result, so there is no honest value for them.
49
+ */
50
+ export async function runWithEvents(loop, req, ctx) {
51
+ ctx.events.emit({
52
+ type: "run.started",
53
+ maxSteps: req.limits.maxSteps,
54
+ maxDurationMs: req.limits.maxDurationMs,
55
+ tools: req.tools.map((t) => t.spec.name),
56
+ });
57
+ let result;
58
+ try {
59
+ result = await loop.run(req, ctx);
60
+ }
61
+ catch (err) {
62
+ ctx.events.emit({
63
+ type: "run.finished",
64
+ outcome: "failed",
65
+ error: err instanceof Error ? err.message : String(err),
66
+ });
67
+ throw err;
68
+ }
69
+ ctx.events.emit({
70
+ type: "run.finished",
71
+ outcome: result.outcome,
72
+ steps: result.steps,
73
+ text: result.text,
74
+ });
75
+ return result;
76
+ }
77
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAyF1C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAS;IAC9B,IAAI,EAAE,QAAQ;IACd,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;CAC3C,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAU,EACV,GAAgB,EAChB,GAAe;IAEf,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QACd,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ;QAC7B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,aAAa;QACvC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;KACzC,CAAC,CAAA;IAEF,IAAI,MAAkB,CAAA;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAA;QACF,MAAM,GAAG,CAAA;IACX,CAAC;IAED,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QACd,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;KAClB,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,65 @@
1
+ import type { AgentMessage, ConverseRequest, ConverseResult, Provider, ToolSpec } from "../providers/types.js";
2
+ import { type ModelCaps, type ModelInvocation } from "./types.js";
3
+ /**
4
+ * A reference to binary content a multimodal request carries. Inline base64 for
5
+ * content the caller already holds; a URL when the provider should fetch it.
6
+ */
7
+ export type BinaryRef = {
8
+ kind: "base64";
9
+ mediaType: string;
10
+ data: string;
11
+ } | {
12
+ kind: "url";
13
+ mediaType?: string;
14
+ url: string;
15
+ };
16
+ /**
17
+ * A non-text part of a chat request. Sending any of these requires
18
+ * `caps.multimodalInput`; a model that lacks it rejects the request rather than
19
+ * quietly dropping the attachment.
20
+ */
21
+ export type ChatPart = {
22
+ type: "image";
23
+ source: BinaryRef;
24
+ } | {
25
+ type: "audio";
26
+ source: BinaryRef;
27
+ } | {
28
+ type: "binary";
29
+ source: BinaryRef;
30
+ };
31
+ /**
32
+ * The `chat` kind's request. It is the existing {@link ConverseRequest} plus an
33
+ * optional multimodal channel, so every current caller's request is already a
34
+ * valid `ChatRequest`.
35
+ */
36
+ export interface ChatRequest extends ConverseRequest {
37
+ /** Attachments for the latest user turn. Requires `caps.multimodalInput`. */
38
+ parts?: ChatPart[];
39
+ }
40
+ /** The `chat` kind's result: prose plus the tool calls that drive the loop. */
41
+ export type ChatResponse = ConverseResult;
42
+ /** The `chat` kind's invocation type. */
43
+ export type ChatModel = ModelInvocation<ChatRequest, ChatResponse>;
44
+ /** The kind string every chat model registers under. */
45
+ export declare const CHAT_KIND = "chat";
46
+ /**
47
+ * Adapt a {@link Provider} onto the model seam as the `chat` kind.
48
+ *
49
+ * This wraps rather than rewrites: the provider's own `converse` still does the
50
+ * work, so the tool-use loop's behaviour is unchanged. What the wrapper adds is
51
+ * a uniform envelope, declared capabilities, and a place for middleware to sit —
52
+ * the same place a perception model sits.
53
+ *
54
+ * Capabilities are reported conservatively, from what the `Provider` interface
55
+ * can actually express: it has no streaming method, no attachment channel and
56
+ * reports no usage, so only `tools` is true. A richer adapter can declare more.
57
+ */
58
+ export declare function chatModel(provider: Provider, caps?: Partial<ModelCaps>): ChatModel;
59
+ /** True when a value is already a model invocation rather than a bare `Provider`. */
60
+ export declare function isChatModel(value: Provider | ChatModel): value is ChatModel;
61
+ /** Accept either a `Provider` or a `ChatModel` and always yield a `ChatModel`. */
62
+ export declare function asChatModel(value: Provider | ChatModel): ChatModel;
63
+ /** Build a `chat` request from the loop's working state. */
64
+ export declare function chatRequest(system: string, messages: AgentMessage[], tools: ToolSpec[], parts?: ChatPart[]): ChatRequest;
65
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/models/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,QAAQ,EACR,QAAQ,EACT,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,eAAe,EAErB,MAAM,YAAY,CAAA;AAEnB;;;GAGG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAAA;AAEzC;;;;GAIG;AACH,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,6EAA6E;IAC7E,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;CACnB;AAED,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG,cAAc,CAAA;AAEzC,yCAAyC;AACzC,MAAM,MAAM,SAAS,GAAG,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;AAElE,wDAAwD;AACxD,eAAO,MAAM,SAAS,SAAS,CAAA;AAE/B;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CAiDtF;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,IAAI,SAAS,CAE3E;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAElE;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,CAAC,EAAE,QAAQ,EAAE,GACjB,WAAW,CAEb"}
@@ -0,0 +1,66 @@
1
+ import { requireCapability, } from "./types.js";
2
+ /** The kind string every chat model registers under. */
3
+ export const CHAT_KIND = "chat";
4
+ /**
5
+ * Adapt a {@link Provider} onto the model seam as the `chat` kind.
6
+ *
7
+ * This wraps rather than rewrites: the provider's own `converse` still does the
8
+ * work, so the tool-use loop's behaviour is unchanged. What the wrapper adds is
9
+ * a uniform envelope, declared capabilities, and a place for middleware to sit —
10
+ * the same place a perception model sits.
11
+ *
12
+ * Capabilities are reported conservatively, from what the `Provider` interface
13
+ * can actually express: it has no streaming method, no attachment channel and
14
+ * reports no usage, so only `tools` is true. A richer adapter can declare more.
15
+ */
16
+ export function chatModel(provider, caps = {}) {
17
+ const resolved = {
18
+ streaming: false,
19
+ tools: true,
20
+ multimodalInput: false,
21
+ usage: false,
22
+ ...caps,
23
+ };
24
+ return {
25
+ id: provider.name,
26
+ kind: CHAT_KIND,
27
+ caps: resolved,
28
+ async invoke(req, ctx) {
29
+ if (req.parts?.length) {
30
+ requireCapability({ id: provider.name, kind: CHAT_KIND, caps: resolved }, "multimodalInput", "request carries parts");
31
+ }
32
+ if (req.tools.length > 0) {
33
+ requireCapability({ id: provider.name, kind: CHAT_KIND, caps: resolved }, "tools", "request carries tool definitions");
34
+ }
35
+ ctx.signal?.throwIfAborted();
36
+ const value = await provider.converse({
37
+ system: req.system,
38
+ messages: req.messages,
39
+ tools: req.tools,
40
+ });
41
+ return { value };
42
+ },
43
+ // The `Provider` interface exposes no health endpoint, so report the honest
44
+ // answer rather than inventing an `up` — a probe that always says "fine" is
45
+ // worse than one that admits it doesn't know.
46
+ probe() {
47
+ return Promise.resolve({
48
+ status: "unknown",
49
+ detail: `provider ${provider.name} exposes no health endpoint`,
50
+ });
51
+ },
52
+ };
53
+ }
54
+ /** True when a value is already a model invocation rather than a bare `Provider`. */
55
+ export function isChatModel(value) {
56
+ return "invoke" in value && "kind" in value && "caps" in value;
57
+ }
58
+ /** Accept either a `Provider` or a `ChatModel` and always yield a `ChatModel`. */
59
+ export function asChatModel(value) {
60
+ return isChatModel(value) ? value : chatModel(value);
61
+ }
62
+ /** Build a `chat` request from the loop's working state. */
63
+ export function chatRequest(system, messages, tools, parts) {
64
+ return { system, messages, tools, ...(parts?.length ? { parts } : {}) };
65
+ }
66
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/models/chat.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,iBAAiB,GAMlB,MAAM,YAAY,CAAA;AAoCnB,wDAAwD;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAA;AAE/B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,QAAkB,EAAE,OAA2B,EAAE;IACzE,MAAM,QAAQ,GAAc;QAC1B,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,IAAI;QACX,eAAe,EAAE,KAAK;QACtB,KAAK,EAAE,KAAK;QACZ,GAAG,IAAI;KACR,CAAA;IAED,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,IAAI;QACjB,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QAEd,KAAK,CAAC,MAAM,CAAC,GAAgB,EAAE,GAAkB;YAC/C,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;gBACtB,iBAAiB,CACf,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EACtD,iBAAiB,EACjB,uBAAuB,CACxB,CAAA;YACH,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,iBAAiB,CACf,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EACtD,OAAO,EACP,kCAAkC,CACnC,CAAA;YACH,CAAC;YACD,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAA;YAE5B,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC;gBACpC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,KAAK,EAAE,GAAG,CAAC,KAAK;aACjB,CAAC,CAAA;YACF,OAAO,EAAE,KAAK,EAAE,CAAA;QAClB,CAAC;QAED,4EAA4E;QAC5E,4EAA4E;QAC5E,8CAA8C;QAC9C,KAAK;YACH,OAAO,OAAO,CAAC,OAAO,CAAC;gBACrB,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,YAAY,QAAQ,CAAC,IAAI,6BAA6B;aAC/D,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,WAAW,CAAC,KAA2B;IACrD,OAAO,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,CAAA;AAChE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,KAA2B;IACrD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,QAAwB,EACxB,KAAiB,EACjB,KAAkB;IAElB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACzE,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { type Health, type HealthStatus, type InvokeContext, type ModelIdentity, type ModelInvocation, type ModelResult, type StreamSink } from "./types.js";
2
+ /** A non-streaming invocation, with its payload types erased. */
3
+ export type Invoker = (req: unknown, ctx: InvokeContext) => Promise<ModelResult<unknown>>;
4
+ /** A streaming invocation, with its payload types erased. */
5
+ export type StreamInvoker = (req: unknown, sink: StreamSink, ctx: InvokeContext) => Promise<ModelResult<unknown>>;
6
+ /**
7
+ * A cross-cutting concern wrapped around an invocation.
8
+ *
9
+ * Middleware is deliberately payload-blind: it sees `unknown` in and
10
+ * `ModelResult<unknown>` out. That is the whole point — tracing, redaction,
11
+ * health tracking and budget enforcement are identical whether the call is a
12
+ * chat completion or an object-detection pass, so one implementation must cover
13
+ * both. A middleware that needs to know the payload type has stopped being
14
+ * cross-cutting and belongs in the kind's own implementation.
15
+ *
16
+ * Both hooks are optional: a middleware that only cares about buffered calls
17
+ * implements `invoke` and leaves streaming untouched.
18
+ */
19
+ export interface Middleware {
20
+ readonly name: string;
21
+ invoke?: (next: Invoker, model: ModelIdentity) => Invoker;
22
+ invokeStream?: (next: StreamInvoker, model: ModelIdentity) => StreamInvoker;
23
+ }
24
+ /**
25
+ * Wrap an invocation in a middleware chain. The first entry is outermost, so
26
+ * `withMiddleware(m, [trace, redact])` runs trace's before-logic first and its
27
+ * after-logic last.
28
+ *
29
+ * The returned invocation keeps the original's `id`, `kind` and `caps`, and
30
+ * only exposes `invokeStream` when the original did — wrapping must never
31
+ * appear to add a capability the underlying model doesn't have.
32
+ */
33
+ export declare function withMiddleware<Req, Res>(model: ModelInvocation<Req, Res>, middleware: Middleware[]): ModelInvocation<Req, Res>;
34
+ /**
35
+ * Ensure every call carries a correlation id, minting one when the caller
36
+ * didn't supply it, so a whole chain of invocations shares a single id. Pass
37
+ * your own `mint` to adopt an ambient trace id from the surrounding request.
38
+ */
39
+ export declare function correlationMiddleware(mint?: () => string): Middleware;
40
+ /** Log each invocation's start, outcome and duration through `ctx.log`. */
41
+ export declare function loggingMiddleware(): Middleware;
42
+ /** Records per-model health derived from invocation outcomes. */
43
+ export declare class HealthTracker {
44
+ private readonly statuses;
45
+ record(model: ModelIdentity, status: HealthStatus, detail?: string): void;
46
+ get(model: ModelIdentity): Health;
47
+ /** Every model this tracker has observed, keyed `kind:id`. */
48
+ snapshot(): Record<string, Health>;
49
+ }
50
+ /**
51
+ * Track reachability from real traffic rather than a separate probe: a
52
+ * successful invocation marks the model up, a thrown one marks it down. This is
53
+ * the generic form of the health bookkeeping a hand-rolled client does inline.
54
+ */
55
+ export declare function healthTrackingMiddleware(tracker: HealthTracker): Middleware;
56
+ /**
57
+ * Map thrown errors through a caller-supplied redactor before they propagate,
58
+ * so a failure carrying a prompt, an image payload or an auth header can be
59
+ * scrubbed once instead of at every catch site. The kernel supplies the seam,
60
+ * never the notion of what is sensitive — that is the application's call.
61
+ */
62
+ export declare function errorRedactionMiddleware(redact: (err: unknown) => unknown): Middleware;
63
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/models/middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;AAEnB,iEAAiE;AACjE,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;AAEzF,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;AAElC;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,KAAK,OAAO,CAAA;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,KAAK,aAAa,CAAA;CAC5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EACrC,KAAK,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAChC,UAAU,EAAE,UAAU,EAAE,GACvB,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAgC3B;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAM,MAA2B,GAAG,UAAU,CASzF;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,IAAI,UAAU,CAwB9C;AAED,iEAAiE;AACjE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAIzE,GAAG,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM;IASjC,8DAA8D;IAC9D,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAGnC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,GAAG,UAAU,CAiB3E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,UAAU,CActF"}
@@ -0,0 +1,139 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { modelIdentity, } from "./types.js";
3
+ /**
4
+ * Wrap an invocation in a middleware chain. The first entry is outermost, so
5
+ * `withMiddleware(m, [trace, redact])` runs trace's before-logic first and its
6
+ * after-logic last.
7
+ *
8
+ * The returned invocation keeps the original's `id`, `kind` and `caps`, and
9
+ * only exposes `invokeStream` when the original did — wrapping must never
10
+ * appear to add a capability the underlying model doesn't have.
11
+ */
12
+ export function withMiddleware(model, middleware) {
13
+ if (middleware.length === 0)
14
+ return model;
15
+ const identity = modelIdentity(model);
16
+ const baseInvoke = (req, ctx) => model.invoke(req, ctx);
17
+ const invoke = middleware
18
+ .filter((m) => m.invoke)
19
+ .reduceRight((next, m) => m.invoke(next, identity), baseInvoke);
20
+ const streaming = model.invokeStream?.bind(model);
21
+ const invokeStream = streaming
22
+ ? middleware
23
+ .filter((m) => m.invokeStream)
24
+ .reduceRight((next, m) => m.invokeStream(next, identity), (req, sink, ctx) => streaming(req, sink, ctx))
25
+ : undefined;
26
+ return {
27
+ id: model.id,
28
+ kind: model.kind,
29
+ caps: model.caps,
30
+ invoke: async (req, ctx) => (await invoke(req, ctx)),
31
+ ...(invokeStream
32
+ ? {
33
+ invokeStream: async (req, sink, ctx) => (await invokeStream(req, sink, ctx)),
34
+ }
35
+ : {}),
36
+ probe: () => model.probe(),
37
+ };
38
+ }
39
+ // ── Built-in middleware ──────────────────────────────────────────────────────
40
+ // Generic mechanics only. Anything that encodes a *policy* (which model to fall
41
+ // back to, what a given product considers sensitive) belongs in an app layer
42
+ // that supplies its own middleware.
43
+ /**
44
+ * Ensure every call carries a correlation id, minting one when the caller
45
+ * didn't supply it, so a whole chain of invocations shares a single id. Pass
46
+ * your own `mint` to adopt an ambient trace id from the surrounding request.
47
+ */
48
+ export function correlationMiddleware(mint = () => randomUUID()) {
49
+ const withId = (ctx) => ctx.correlationId ? ctx : { ...ctx, correlationId: mint() };
50
+ return {
51
+ name: "correlation",
52
+ invoke: (next) => (req, ctx) => next(req, withId(ctx)),
53
+ invokeStream: (next) => (req, sink, ctx) => next(req, sink, withId(ctx)),
54
+ };
55
+ }
56
+ /** Log each invocation's start, outcome and duration through `ctx.log`. */
57
+ export function loggingMiddleware() {
58
+ const run = async (model, ctx, call) => {
59
+ const started = Date.now();
60
+ const label = `${model.kind}:${model.id}`;
61
+ try {
62
+ const result = await call();
63
+ ctx.log(`model ${label} ok (${Date.now() - started}ms)`);
64
+ return result;
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ ctx.log(`model ${label} error after ${Date.now() - started}ms: ${message}`);
69
+ throw err;
70
+ }
71
+ };
72
+ return {
73
+ name: "logging",
74
+ invoke: (next, model) => (req, ctx) => run(model, ctx, () => next(req, ctx)),
75
+ invokeStream: (next, model) => (req, sink, ctx) => run(model, ctx, () => next(req, sink, ctx)),
76
+ };
77
+ }
78
+ /** Records per-model health derived from invocation outcomes. */
79
+ export class HealthTracker {
80
+ statuses = new Map();
81
+ record(model, status, detail) {
82
+ this.statuses.set(`${model.kind}:${model.id}`, { status, ...(detail ? { detail } : {}) });
83
+ }
84
+ get(model) {
85
+ return (this.statuses.get(`${model.kind}:${model.id}`) ?? {
86
+ status: "unknown",
87
+ detail: "no invocation recorded yet",
88
+ });
89
+ }
90
+ /** Every model this tracker has observed, keyed `kind:id`. */
91
+ snapshot() {
92
+ return Object.fromEntries(this.statuses);
93
+ }
94
+ }
95
+ /**
96
+ * Track reachability from real traffic rather than a separate probe: a
97
+ * successful invocation marks the model up, a thrown one marks it down. This is
98
+ * the generic form of the health bookkeeping a hand-rolled client does inline.
99
+ */
100
+ export function healthTrackingMiddleware(tracker) {
101
+ const run = async (model, call) => {
102
+ try {
103
+ const result = await call();
104
+ tracker.record(model, "up");
105
+ return result;
106
+ }
107
+ catch (err) {
108
+ tracker.record(model, "down", err instanceof Error ? err.message : String(err));
109
+ throw err;
110
+ }
111
+ };
112
+ return {
113
+ name: "health-tracking",
114
+ invoke: (next, model) => (req, ctx) => run(model, () => next(req, ctx)),
115
+ invokeStream: (next, model) => (req, sink, ctx) => run(model, () => next(req, sink, ctx)),
116
+ };
117
+ }
118
+ /**
119
+ * Map thrown errors through a caller-supplied redactor before they propagate,
120
+ * so a failure carrying a prompt, an image payload or an auth header can be
121
+ * scrubbed once instead of at every catch site. The kernel supplies the seam,
122
+ * never the notion of what is sensitive — that is the application's call.
123
+ */
124
+ export function errorRedactionMiddleware(redact) {
125
+ const run = async (call) => {
126
+ try {
127
+ return await call();
128
+ }
129
+ catch (err) {
130
+ throw redact(err);
131
+ }
132
+ };
133
+ return {
134
+ name: "error-redaction",
135
+ invoke: (next) => (req, ctx) => run(() => next(req, ctx)),
136
+ invokeStream: (next) => (req, sink, ctx) => run(() => next(req, sink, ctx)),
137
+ };
138
+ }
139
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/models/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { Middleware } from "./middleware.js";
2
+ import type { ModelInvocation, ModelKind } from "./types.js";
3
+ /**
4
+ * A model reference: which kind, and which implementation within that kind.
5
+ * This is the routing key — everything the registry needs to bind a concrete
6
+ * invocation.
7
+ */
8
+ export interface ModelRef {
9
+ kind: ModelKind;
10
+ id: string;
11
+ }
12
+ /** Builds an invocation on demand, so registration stays cheap and lazy. */
13
+ export type ModelFactory<Req, Res> = () => ModelInvocation<Req, Res>;
14
+ /** The outcome of a capability check, mirroring `AgentEngine.supports()`. */
15
+ export type ModelSupport = {
16
+ ok: true;
17
+ } | {
18
+ ok: false;
19
+ reason: string;
20
+ };
21
+ /** Thrown when a `kind`+`id` has no registered implementation. */
22
+ export declare class UnknownModelError extends Error {
23
+ readonly ref: ModelRef;
24
+ constructor(ref: ModelRef, known: string[]);
25
+ }
26
+ /**
27
+ * Binds `kind`+`id` to a concrete {@link ModelInvocation}.
28
+ *
29
+ * This is the **low** layer of routing: it answers "which implementation is
30
+ * `vision.detect:yolo-v8`?", not "which model should capability X use?" — that
31
+ * second question belongs to a `RouteResolver`, which sits above and produces
32
+ * the {@link ModelRef} this registry then binds.
33
+ *
34
+ * Registering a new kind requires no kernel change: an application registers
35
+ * its own factory and resolves it back through the same call the built-in chat
36
+ * models use. That is what makes a perception layer an extension rather than a
37
+ * fork.
38
+ */
39
+ export declare class ModelRegistry {
40
+ private readonly factories;
41
+ private readonly middleware;
42
+ /**
43
+ * Register an implementation. Re-registering the same `kind`+`id` throws:
44
+ * a silent overwrite is how two layers end up fighting over one route.
45
+ */
46
+ register<Req, Res>(ref: ModelRef, factory: ModelFactory<Req, Res>): this;
47
+ /** Register an already-constructed invocation under its own `kind`/`id`. */
48
+ registerInstance<Req, Res>(model: ModelInvocation<Req, Res>): this;
49
+ /**
50
+ * Add middleware applied to every invocation this registry hands out. Order is
51
+ * outermost-first, so the first registered middleware sees a call before the
52
+ * ones after it.
53
+ */
54
+ use(...middleware: Middleware[]): this;
55
+ has(ref: ModelRef): boolean;
56
+ /** Whether this registry can serve a ref, with a reason when it cannot. */
57
+ supports(ref: ModelRef): ModelSupport;
58
+ /**
59
+ * Bind a ref to its invocation, with this registry's middleware applied.
60
+ *
61
+ * The type parameters are the **caller's assertion** about the kind's
62
+ * contract — the registry stores implementations kind-erased, so asking for
63
+ * `resolve<ChatRequest, ChatResponse>({kind: "vision.detect", ...})` compiles
64
+ * but is a bug. Resolve through a kind's own helper where one exists.
65
+ *
66
+ * Fails loud on an unregistered ref rather than returning undefined: an
67
+ * unresolved route is a configuration error, and the earliest possible throw
68
+ * is the most useful one.
69
+ */
70
+ resolve<Req, Res>(ref: ModelRef): ModelInvocation<Req, Res>;
71
+ /** Every registered kind, in registration order. */
72
+ kinds(): ModelKind[];
73
+ /** The ids registered under one kind. */
74
+ ids(kind: ModelKind): string[];
75
+ /** Every registered ref rendered as `kind:id`, for diagnostics. */
76
+ registered(): string[];
77
+ }
78
+ //# sourceMappingURL=registry.d.ts.map