@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.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/agent.d.ts +71 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +258 -0
- package/dist/agent.js.map +1 -0
- package/dist/connectors/mcpClient.d.ts +22 -0
- package/dist/connectors/mcpClient.d.ts.map +1 -0
- package/dist/connectors/mcpClient.js +63 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/context/types.d.ts +68 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +43 -0
- package/dist/context/types.js.map +1 -0
- package/dist/engines/capability.d.ts +67 -0
- package/dist/engines/capability.d.ts.map +1 -0
- package/dist/engines/capability.js +73 -0
- package/dist/engines/capability.js.map +1 -0
- package/dist/engines/capabilityEmissions.d.ts +27 -0
- package/dist/engines/capabilityEmissions.d.ts.map +1 -0
- package/dist/engines/capabilityEmissions.js +37 -0
- package/dist/engines/capabilityEmissions.js.map +1 -0
- package/dist/engines/capabilityMcp.d.ts +23 -0
- package/dist/engines/capabilityMcp.d.ts.map +1 -0
- package/dist/engines/capabilityMcp.js +36 -0
- package/dist/engines/capabilityMcp.js.map +1 -0
- package/dist/engines/capabilityStdio.d.ts +46 -0
- package/dist/engines/capabilityStdio.d.ts.map +1 -0
- package/dist/engines/capabilityStdio.js +39 -0
- package/dist/engines/capabilityStdio.js.map +1 -0
- package/dist/engines/claudeCode.d.ts +73 -0
- package/dist/engines/claudeCode.d.ts.map +1 -0
- package/dist/engines/claudeCode.js +100 -0
- package/dist/engines/claudeCode.js.map +1 -0
- package/dist/engines/claudeCodeDriver.d.ts +14 -0
- package/dist/engines/claudeCodeDriver.d.ts.map +1 -0
- package/dist/engines/claudeCodeDriver.js +90 -0
- package/dist/engines/claudeCodeDriver.js.map +1 -0
- package/dist/engines/codex.d.ts +72 -0
- package/dist/engines/codex.d.ts.map +1 -0
- package/dist/engines/codex.js +157 -0
- package/dist/engines/codex.js.map +1 -0
- package/dist/engines/codexDriver.d.ts +43 -0
- package/dist/engines/codexDriver.d.ts.map +1 -0
- package/dist/engines/codexDriver.js +110 -0
- package/dist/engines/codexDriver.js.map +1 -0
- package/dist/engines/connectorMcp.d.ts +60 -0
- package/dist/engines/connectorMcp.d.ts.map +1 -0
- package/dist/engines/connectorMcp.js +137 -0
- package/dist/engines/connectorMcp.js.map +1 -0
- package/dist/engines/index.d.ts +33 -0
- package/dist/engines/index.d.ts.map +1 -0
- package/dist/engines/index.js +29 -0
- package/dist/engines/index.js.map +1 -0
- package/dist/engines/native.d.ts +55 -0
- package/dist/engines/native.d.ts.map +1 -0
- package/dist/engines/native.js +117 -0
- package/dist/engines/native.js.map +1 -0
- package/dist/engines/types.d.ts +107 -0
- package/dist/engines/types.d.ts.map +1 -0
- package/dist/engines/types.js +2 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/events.d.ts +211 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +69 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/loop.d.ts +132 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +77 -0
- package/dist/loop.js.map +1 -0
- package/dist/models/chat.d.ts +65 -0
- package/dist/models/chat.d.ts.map +1 -0
- package/dist/models/chat.js +66 -0
- package/dist/models/chat.js.map +1 -0
- package/dist/models/middleware.d.ts +63 -0
- package/dist/models/middleware.d.ts.map +1 -0
- package/dist/models/middleware.js +139 -0
- package/dist/models/middleware.js.map +1 -0
- package/dist/models/registry.d.ts +78 -0
- package/dist/models/registry.d.ts.map +1 -0
- package/dist/models/registry.js +0 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/models/types.d.ts +149 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +64 -0
- package/dist/models/types.js.map +1 -0
- package/dist/primitives/codeExec.d.ts +25 -0
- package/dist/primitives/codeExec.d.ts.map +1 -0
- package/dist/primitives/codeExec.js +69 -0
- package/dist/primitives/codeExec.js.map +1 -0
- package/dist/primitives/download.d.ts +19 -0
- package/dist/primitives/download.d.ts.map +1 -0
- package/dist/primitives/download.js +113 -0
- package/dist/primitives/download.js.map +1 -0
- package/dist/primitives/fs.d.ts +5 -0
- package/dist/primitives/fs.d.ts.map +1 -0
- package/dist/primitives/fs.js +59 -0
- package/dist/primitives/fs.js.map +1 -0
- package/dist/primitives/http.d.ts +100 -0
- package/dist/primitives/http.d.ts.map +1 -0
- package/dist/primitives/http.js +257 -0
- package/dist/primitives/http.js.map +1 -0
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +90 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/clientOptions.d.ts +14 -0
- package/dist/providers/clientOptions.d.ts.map +1 -0
- package/dist/providers/clientOptions.js +26 -0
- package/dist/providers/clientOptions.js.map +1 -0
- package/dist/providers/index.d.ts +24 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +51 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/mock.d.ts +25 -0
- package/dist/providers/mock.d.ts.map +1 -0
- package/dist/providers/mock.js +85 -0
- package/dist/providers/mock.js.map +1 -0
- package/dist/providers/openai.d.ts +32 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +118 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +29 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +35 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/types.d.ts +60 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/routing/staticResolver.d.ts +41 -0
- package/dist/routing/staticResolver.d.ts.map +1 -0
- package/dist/routing/staticResolver.js +67 -0
- package/dist/routing/staticResolver.js.map +1 -0
- package/dist/routing/types.d.ts +72 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +11 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/secrets.d.ts +8 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +14 -0
- package/dist/secrets.js.map +1 -0
- package/dist/tools/metadata.d.ts +93 -0
- package/dist/tools/metadata.d.ts.map +1 -0
- package/dist/tools/metadata.js +49 -0
- package/dist/tools/metadata.js.map +1 -0
- package/dist/tools/modelTool.d.ts +35 -0
- package/dist/tools/modelTool.d.ts.map +1 -0
- package/dist/tools/modelTool.js +37 -0
- package/dist/tools/modelTool.js.map +1 -0
- package/dist/tools/registry.d.ts +65 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +166 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- 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
|
package/dist/loop.js.map
ADDED
|
@@ -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
|