@slopus/happy-agent-base 0.0.0 → 0.0.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -16
  3. package/dist/Agent.d.ts +31 -0
  4. package/dist/Agent.d.ts.map +1 -0
  5. package/dist/Agent.js +119 -0
  6. package/dist/Agent.js.map +1 -0
  7. package/dist/AgentBase.d.ts +93 -0
  8. package/dist/AgentBase.d.ts.map +1 -0
  9. package/dist/AgentBase.js +909 -0
  10. package/dist/AgentBase.js.map +1 -0
  11. package/dist/AgentBaseContext.d.ts +22 -0
  12. package/dist/AgentBaseContext.d.ts.map +1 -0
  13. package/dist/AgentBaseContext.js +33 -0
  14. package/dist/AgentBaseContext.js.map +1 -0
  15. package/dist/AgentBaseHooks.d.ts +68 -0
  16. package/dist/AgentBaseHooks.d.ts.map +1 -0
  17. package/dist/AgentBaseHooks.js +2 -0
  18. package/dist/AgentBaseHooks.js.map +1 -0
  19. package/dist/AgentBasePersistence.d.ts +60 -0
  20. package/dist/AgentBasePersistence.d.ts.map +1 -0
  21. package/dist/AgentBasePersistence.js +2 -0
  22. package/dist/AgentBasePersistence.js.map +1 -0
  23. package/dist/AgentBaseState.d.ts +11 -0
  24. package/dist/AgentBaseState.d.ts.map +1 -0
  25. package/dist/AgentBaseState.js +2 -0
  26. package/dist/AgentBaseState.js.map +1 -0
  27. package/dist/AgentFeature.d.ts +29 -0
  28. package/dist/AgentFeature.d.ts.map +1 -0
  29. package/dist/AgentFeature.js +2 -0
  30. package/dist/AgentFeature.js.map +1 -0
  31. package/dist/AgentFeatureAction.d.ts +16 -0
  32. package/dist/AgentFeatureAction.d.ts.map +1 -0
  33. package/dist/AgentFeatureAction.js +2 -0
  34. package/dist/AgentFeatureAction.js.map +1 -0
  35. package/dist/AgentProviders.d.ts +17 -0
  36. package/dist/AgentProviders.d.ts.map +1 -0
  37. package/dist/AgentProviders.js +29 -0
  38. package/dist/AgentProviders.js.map +1 -0
  39. package/dist/AgentTool.d.ts +47 -0
  40. package/dist/AgentTool.d.ts.map +1 -0
  41. package/dist/AgentTool.js +5 -0
  42. package/dist/AgentTool.js.map +1 -0
  43. package/dist/index.d.ts +10 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +10 -1
  46. package/dist/index.js.map +1 -1
  47. package/package.json +42 -37
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rig Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,26 +1,251 @@
1
1
  # @slopus/happy-agent-base
2
2
 
3
- Shared foundations for Happy coding agents.
3
+ The minimal core of a Happy coding agent.
4
4
 
5
- The package is bootstrapped with an empty public API. Future agent-base behavior belongs in
6
- `sources` and is exported through `sources/index.ts`.
5
+ `AgentBase` is one agent session over one `@slopus/happy-providers` provider. Messages arrive
6
+ through two FIFO queues — steering and send the loop replays the full conversation to the
7
+ provider session, and the streamed events are forwarded to the optional hooks while the
8
+ assistant reply joins the history. When the model stops for tool calls, the agent executes them
9
+ and feeds the results back. The conversation is observable only through hooks; there is no
10
+ external transcript or status surface.
7
11
 
8
- ```text
9
- sources/index.ts
10
- |
11
- v
12
- dist/index.js
12
+ ## Core API
13
+
14
+ ```ts
15
+ class AgentBase {
16
+ constructor(ctx: Context, options: AgentBaseOptions);
17
+
18
+ readonly id: string;
19
+ readonly state: AgentBaseState; // the agent's own copy, mutable directly
20
+
21
+ steer(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
22
+ send(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
23
+ start(): void;
24
+ abort(): Promise<void>;
25
+ compact(ctx: Context): Promise<void>;
26
+ waitForIdle(): Promise<void>;
27
+ close(): Promise<void>;
28
+ }
29
+
30
+ interface AgentBaseOptions {
31
+ id: string;
32
+ providers: AgentProviders;
33
+ provider: string; // registry ID; serializable alongside model and effort
34
+ persistence: AgentBasePersistence;
35
+ hooks?: AgentBaseHooks;
36
+ initialState?: Partial<AgentBaseState>; // copied into the agent's own state
37
+ model?: string;
38
+ effort?: SessionReasoningEffort;
39
+ serviceTier?: SessionServiceTier;
40
+ steeringMode?: "one-at-a-time" | "all"; // default "one-at-a-time"
41
+ sendMode?: "one-at-a-time" | "all"; // default "one-at-a-time"
42
+ }
43
+
44
+ interface AgentBaseState {
45
+ instructions: string;
46
+ tools: AnyAgentTool[];
47
+ }
48
+
49
+ interface AgentBaseHooks {
50
+ onEvent?: (ctx: Context, event: SessionEvent) => void;
51
+ instructions?: (ctx: Context) => string;
52
+ tools?: (ctx: Context) => readonly AnyAgentTool[];
53
+ modelChanged?: (ctx: Context, change: AgentBaseModelChange) => SessionSystemMessage | undefined;
54
+ beforeAgentLoop?: (ctx: Context) => void;
55
+ beforeTurn?: (ctx: Context) => void;
56
+ beforeInference?: (ctx: Context) => void;
57
+ afterInference?: (ctx: Context) => void;
58
+ afterTurn?: (ctx: Context) => readonly AgentFeatureAction[] | undefined;
59
+ afterAgentLoop?: (ctx: Context) => readonly AgentFeatureAction[] | undefined;
60
+ }
61
+
62
+ interface AgentBaseModelChange {
63
+ previousModel: string | undefined;
64
+ model: string;
65
+ previousProvider: string;
66
+ provider: string;
67
+ providers: AgentProviders;
68
+ previousProviderInstance: BaseProvider | null;
69
+ providerInstance: BaseProvider | null;
70
+ wasReset: boolean; // the change was incompatible and the history was erased
71
+ }
72
+
73
+ interface AgentBaseMessageOptions {
74
+ provider?: string; // registry ID to switch to
75
+ model?: string;
76
+ effort?: SessionReasoningEffort;
77
+ serviceTier?: SessionServiceTier;
78
+ }
79
+
80
+ type AgentFeatureAction =
81
+ | { type: "steer"; message: SessionUserMessage }
82
+ | { type: "send"; message: SessionUserMessage }
83
+ | { type: "compact" };
84
+
85
+ interface AgentTool<Args extends TSchema = TSchema, Result extends TSchema = TSchema> {
86
+ // The provider-facing descriptor fields of SessionTool, with parameters typed as Args, plus:
87
+ durable?: boolean;
88
+ returnType: Result;
89
+ execute(ctx: Context, args: Static<Args>): Promise<Static<Result>>;
90
+ toLLM(result: Static<Result>): readonly SessionOutputBlock[];
91
+ isError?(result: Static<Result>): boolean;
92
+ }
93
+
94
+ function defineAgentTool<const Args extends TSchema, const Result extends TSchema>(
95
+ tool: AgentTool<Args, Result>,
96
+ ): AgentTool<Args, Result>;
97
+
98
+ interface AgentBasePersistence {
99
+ transaction<Result>(ctx: Context, work: (ctx: Context) => Promise<Result>): Promise<Result>;
100
+ load(ctx: Context): Promise<readonly AgentBaseRecord[]>;
101
+ append(ctx: Context, record: AgentBaseRecord): Promise<void>;
102
+ clearRecords(ctx: Context): Promise<void>; // physical delete, used inside the compaction transaction
103
+ readValues(ctx: Context, prefix: string): Promise<readonly { key: string; value: unknown }[]>;
104
+ writeValue(ctx: Context, key: string, value: unknown): Promise<void>;
105
+ deleteValue(ctx: Context, key: string): Promise<void>;
106
+ }
13
107
  ```
14
108
 
15
- ## Releasing
109
+ Persistence is an append-only main context store plus a sorted key-value store alongside it. The
110
+ agent serializes every operation through one internal lock (configured to crash on re-entry), so
111
+ implementations never see concurrent calls and need no locking of their own.
112
+
113
+ A queued message is written under a `steering.` or `send.` key ordered by append time;
114
+ nothing that is not yet part of the context reaches the main store. When a queue drains, each
115
+ consumed message is appended as a user record and its queue key deleted, all inside one
116
+ `transaction` so a crash or failure can never leave a message in both stores or neither, and
117
+ only then does inference run on the resulting context. Transactions are completely transparent
118
+ to the agent: the implementation opens one, hands work a derived context its own operations
119
+ recognize, and carries the transaction on that context however it likes; work resolving
120
+ commits, a thrown error rolls back. Assistant output is appended one finished block at a time as
121
+ it streams, so main-store records always arrive in context order and consecutive block records
122
+ reassemble into one assistant message on load. The agent loop loads everything once, on the first
123
+ inference attempt; the load result replaces the in-memory state, including leftover queued
124
+ messages from an earlier process, which join the next turn. A failed load is reported as an
125
+ `internal_error` done event and is not sticky: the next requested turn retries it, with every
126
+ queued message still safely waiting.
127
+
128
+ The two queues give four delivery strategies, mirroring Pi:
16
129
 
17
- From a clean `main` worktree whose `HEAD` matches `origin/main`, run:
130
+ | Strategy | Behavior |
131
+ |---|---|
132
+ | Steering + one-at-a-time | `steer` queues FIFO. After the current assistant response and all its tool calls finish, the oldest message injects, gets a response, then the next is handled. |
133
+ | Steering + all | After the current response and tool batch finish, every queued steering message injects together before one response. |
134
+ | Send + one-at-a-time | `send` waits until the agent would otherwise stop — no tool calls or steering remain — then injects one message and waits for its response before draining another. |
135
+ | Send + all | Once the agent would otherwise stop, every queued sent message injects together before one response. |
136
+
137
+ Both modes default to `"one-at-a-time"`, and steering always takes precedence over sent messages.
138
+ Queue consumption happens only between inferences — never mid-stream and never during a tool
139
+ batch — so an injected message can never interleave with an active response's block records.
140
+
141
+ `steer` and `send` resolve once the durable queue write lands, so a failed write keeps the
142
+ message out of the conversation; they wait neither for the history load nor for the turn. Each
143
+ message may carry its own inference settings — provider, model, effort, and service tier —
144
+ which take effect when the message is consumed and stay effective for every later message that does not
145
+ override them, surviving restarts through a durable settings entry. A message without settings
146
+ uses the previously effective values, or the constructor defaults when nothing was ever carried;
147
+ relying on those defaults is discouraged — prefer sending settings with the message.
148
+
149
+ A provider or model change is checked against the provider-model compatibility matrix from
150
+ `@slopus/happy-providers`, using the compatibility types the providers were registered with. A
151
+ compatible change keeps the conversation; a compatible provider change still gets a fresh
152
+ session on the new provider, since a session is bound to the provider that created it. An
153
+ incompatible change — including a switch to a different provider of the same type, or to an
154
+ unregistered ID — resets the conversation: the durable history is erased completely, the old
155
+ provider session is destroyed, and a fresh session serves the new selection. The `modelChanged`
156
+ hook fires on every selection change with the old and new model, both provider IDs and live
157
+ instances, the registry, and the `wasReset` flag; on a reset the handoff system message it
158
+ returns is injected at the very beginning of the fresh context — without one the context starts
159
+ completely empty. The consumed message that carried the new selection follows the handoff. A
160
+ thrown provider or load failure is reported to the `onEvent` hook as an `internal_error` done
161
+ event instead of rejecting the loop. The agent never retries inference itself — providers own
162
+ retry semantics and surface them as `retrying` events. A provider-reported error response ends
163
+ that response but not the turn: messages still queued drain into a fresh inference, each drain
164
+ consuming from a finite queue, so a persistently failing provider cannot loop.
165
+
166
+ A turn that ends failed surfaces its error to the context as a durable system message
167
+ (`The last turn failed: <message>`), so the next inference sees what went wrong. Only
168
+ unrecovered failures leave this trace: a provider-reported error followed by a successful
169
+ response in the same turn recovers silently, and a failed history load appends nothing since
170
+ there is no loaded context to append to.
171
+
172
+ When a turn stops for tool calls, every call in the batch runs in parallel. Arguments are
173
+ validated against the tool's TypeBox `parameters` schema before `execute` runs, so `execute`
174
+ receives them as `Static<Args>` rather than unknown. `execute` returns a structured result that
175
+ is validated against `returnType` and then rendered into output blocks for the model with
176
+ `toLLM`; an optional `isError` predicate marks a structured result as an error. A missing tool,
177
+ invalid JSON arguments, arguments that fail the schema, an incomplete call, a thrown `execute`,
178
+ or a result that fails `returnType` becomes an error tool result
179
+ (`isError: true`) for the model instead of failing the run; provider-settled server calls are
180
+ never executed by the agent, and their streamed `toolcall_result_*` events are simply ignored —
181
+ the server call block stays in the history, the events reach the hooks, and no tool result
182
+ message is stored or owed.
183
+
184
+ Before any call in a batch executes, the whole batch is committed to the sorted store under
185
+ `tool.` keys ordered by position, so a crash mid-batch leaves a durable record of the calls still
186
+ owed a result. Calls run in parallel, but results land strictly in call order: a finished result
187
+ waits until every earlier call in the batch has committed, and each commit appends the `tool`
188
+ record and deletes the pending entry in one transaction. Once the batch is complete the loop runs
189
+ inference again with the full context.
190
+
191
+ `start` begins the loop without a new message: it loads the durable state and continues a turn
192
+ that was cut off by a crash — leftover queued messages are consumed, a dispatched `tool.`
193
+ batch is settled, and an unanswered user or tool message gets its inference. When an interrupted
194
+ batch resumes, only tools marked `durable: true` execute again; every other interrupted call
195
+ becomes an error tool result, since the agent cannot know whether its side effects already
196
+ happened. On an idle history `start` loads and does nothing more.
197
+
198
+ `compact` compacts the conversation through the provider session. It waits for the active turn
199
+ to end — including queued messages already draining — or runs right away when idle, snapshots
200
+ the history, and asks the provider to compact it. The completed replacement context supersedes
201
+ the compacted history while any message that joined after the snapshot is kept. In one atomic
202
+ transaction the superseded records are physically deleted and the replacement — the messages
203
+ that stay — is appended as a `compaction` record, which then opens the store while later
204
+ records append as usual. Calls made while a compaction is pending or running
205
+ await that same shared compaction; it resolves on completion and rejects when the provider
206
+ reports failure, leaving the history untouched.
207
+
208
+ Hooks receive the agent's context first. That context — shared by tool executions — is derived
209
+ once at construction and carries the agent's provider registry ID, model, effort, and
210
+ service tier — all serializable values — readable through the exported `agentBaseProvider`,
211
+ `agentBaseModel`, `agentBaseEffort`, and `agentBaseServiceTier` accessors. The
212
+ `instructions` and `tools` hooks, when provided, answer for the session: they are consulted for
213
+ session creation, every inference request, compaction, and tool lookup, superseding
214
+ `state.instructions` and `state.tools`. A hook that throws falls back to the state and never
215
+ fails the run.
216
+
217
+ The lifecycle hooks bracket the loop's own structure. `beforeAgentLoop` fires when the loop
218
+ leaves the settled state and begins working, and `afterAgentLoop` fires when it would settle
219
+ back to idle; between them, each turn is bracketed by `beforeTurn` and `afterTurn`, and each
220
+ inference request inside a turn by `beforeInference` and `afterInference`. `afterTurn` and
221
+ `afterAgentLoop` may return an array of `AgentFeatureAction`s, all applied together before the
222
+ loop continues: `steer` and `send` queue a message through the ordinary durable queues exactly
223
+ as the public methods do, and `compact` triggers the shared compaction. Actions from `afterTurn`
224
+ drive the loop into another turn within the same loop span; actions from `afterAgentLoop` reopen
225
+ the loop instead of settling. Like every hook, a thrown lifecycle hook — or a failing action —
226
+ never fails the run.
227
+
228
+ `abort` cancels the active turn and resolves once the loop has stopped; when idle it is a no-op.
229
+ The inference stream is abandoned and asked to close, a `done` event with state `cancelled` is
230
+ emitted, blocks that already finished stay in the history while an unfinished block is dropped
231
+ everywhere, and each still-running tool call settles as an error tool result saying it was
232
+ aborted — consuming its pending `tool.` entry so the batch leaves a complete context behind.
233
+ The queued turn request is dropped too, but messages still waiting in the steering and
234
+ send queues stay durable and join the next requested turn.
235
+
236
+ `AgentProviders` is a mutable registry of provider instances keyed by caller-supplied IDs, so the
237
+ same provider class can be registered under several IDs. `add(id, provider, type)` registers an
238
+ instance together with its compatibility type (`"claude"`, `"codex"`, `"grok"`, `"bedrock"`, or
239
+ `"gym"`), `get(id)` returns the provider or null, and `typeOf(id)` returns the registered type
240
+ or null. The agent is configured entirely with serializable values — a
241
+ provider registry ID, a model name, an effort level, and a service tier — and resolves the live provider from
242
+ the registry when the session is first created; an ID that is not registered at that moment
243
+ fails the turn like any thrown error.
244
+
245
+ ## Validation
18
246
 
19
247
  ```sh
20
- pnpm release:happy-agent-base:patch
248
+ pnpm --filter @slopus/happy-agent-base check
249
+ pnpm --filter @slopus/happy-agent-base test
250
+ pnpm --filter @slopus/happy-agent-base build
21
251
  ```
22
-
23
- The local release validates, tests, and builds only `@slopus/happy-agent-base`, then creates and
24
- pushes a `happy-agent-base-vX.Y.Z` tag. The shared GitHub publish workflow again validates, tests,
25
- and builds only this package before publishing it through npm trusted publishing. The npm trusted
26
- publisher must identify `slopus/rig`, `publish.yml`, and the `npm` GitHub environment.
@@ -0,0 +1,31 @@
1
+ import type { SessionUserMessage } from "@slopus/happy-providers";
2
+ import type { Context } from "@steve.kite/stdlib";
3
+ import { type AgentBaseMessageOptions, type AgentBaseOptions } from "./AgentBase.js";
4
+ import type { AgentBaseState } from "./AgentBaseState.js";
5
+ import type { AgentFeature } from "./AgentFeature.js";
6
+ import type { AnyAgentTool } from "./AgentTool.js";
7
+ export interface AgentOptions<Tool extends AnyAgentTool = AnyAgentTool> extends Omit<AgentBaseOptions, "hooks"> {
8
+ /** Independent capabilities whose hook implementations are merged, in array order. */
9
+ readonly features?: readonly AgentFeature<Tool>[];
10
+ }
11
+ /**
12
+ * A thin wrapper around `AgentBase` that assembles its behavior from features. Each feature
13
+ * implements any subset of the agent hooks on its own; the agent merges them into the singular
14
+ * private hooks its internal base runs with. Observing hooks fan out to every feature in array
15
+ * order, instructions concatenate, tools concatenate, lifecycle actions concatenate, and the
16
+ * first feature to answer a reset injection wins.
17
+ */
18
+ export declare class Agent<Tool extends AnyAgentTool = AnyAgentTool> {
19
+ #private;
20
+ constructor(ctx: Context, options: AgentOptions<Tool>);
21
+ get id(): string;
22
+ get state(): AgentBaseState;
23
+ steer(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
24
+ send(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
25
+ start(): void;
26
+ waitForIdle(): Promise<void>;
27
+ compact(ctx: Context): Promise<void>;
28
+ abort(): Promise<void>;
29
+ close(): Promise<void>;
30
+ }
31
+ //# sourceMappingURL=Agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Agent.d.ts","sourceRoot":"","sources":["../sources/Agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAwB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACxF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAa,KAAK,uBAAuB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEhG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY,CAClE,SAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACvC,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;CACrD;AAED;;;;;;GAMG;AACH,qBAAa,KAAK,CAAC,IAAI,SAAS,YAAY,GAAG,YAAY;;IAGvD,YAAY,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAMpD;IAED,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,IAAI,KAAK,IAAI,cAAc,CAE1B;IAEK,KAAK,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC,CAEf;IAEK,IAAI,CACN,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC,CAEf;IAED,KAAK,IAAI,IAAI,CAEZ;IAEK,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjC;IAEK,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzC;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CACJ"}
package/dist/Agent.js ADDED
@@ -0,0 +1,119 @@
1
+ import { AgentBase } from "./AgentBase.js";
2
+ /**
3
+ * A thin wrapper around `AgentBase` that assembles its behavior from features. Each feature
4
+ * implements any subset of the agent hooks on its own; the agent merges them into the singular
5
+ * private hooks its internal base runs with. Observing hooks fan out to every feature in array
6
+ * order, instructions concatenate, tools concatenate, lifecycle actions concatenate, and the
7
+ * first feature to answer a reset injection wins.
8
+ */
9
+ export class Agent {
10
+ #base;
11
+ constructor(ctx, options) {
12
+ const { features, ...base } = options;
13
+ this.#base = new AgentBase(ctx, {
14
+ ...base,
15
+ hooks: mergeFeatures(features ?? []),
16
+ });
17
+ }
18
+ get id() {
19
+ return this.#base.id;
20
+ }
21
+ get state() {
22
+ return this.#base.state;
23
+ }
24
+ async steer(ctx, message, options) {
25
+ await this.#base.steer(ctx, message, options);
26
+ }
27
+ async send(ctx, message, options) {
28
+ await this.#base.send(ctx, message, options);
29
+ }
30
+ start() {
31
+ this.#base.start();
32
+ }
33
+ async waitForIdle() {
34
+ await this.#base.waitForIdle();
35
+ }
36
+ async compact(ctx) {
37
+ await this.#base.compact(ctx);
38
+ }
39
+ async abort() {
40
+ await this.#base.abort();
41
+ }
42
+ async close() {
43
+ await this.#base.close();
44
+ }
45
+ }
46
+ /**
47
+ * Merge every feature's hook implementations into one `AgentBaseHooks`. A hook is provided only
48
+ * when at least one feature implements it, so the base's own fallbacks — such as the mutable
49
+ * state for instructions and tools — stay in effect otherwise.
50
+ */
51
+ function mergeFeatures(features) {
52
+ const withInstructions = features.filter((feature) => feature.instructions !== undefined);
53
+ const withTools = features.filter((feature) => feature.tools !== undefined);
54
+ const withModelChanged = features.filter((feature) => feature.modelChanged !== undefined);
55
+ const fanOut = (pick) => {
56
+ const implemented = features.filter((feature) => pick(feature) !== undefined);
57
+ if (implemented.length === 0)
58
+ return undefined;
59
+ return (ctx) => {
60
+ for (const feature of implemented)
61
+ pick(feature)?.(ctx);
62
+ };
63
+ };
64
+ const collect = (pick) => {
65
+ const implemented = features.filter((feature) => pick(feature) !== undefined);
66
+ if (implemented.length === 0)
67
+ return undefined;
68
+ return (ctx) => implemented.flatMap((feature) => pick(feature)?.(ctx) ?? []);
69
+ };
70
+ const onEvent = fanOutEvent(features);
71
+ return {
72
+ ...(onEvent === undefined ? {} : { onEvent }),
73
+ ...(withInstructions.length === 0
74
+ ? {}
75
+ : {
76
+ instructions: (ctx) => withInstructions
77
+ .map((feature) => feature.instructions?.(ctx) ?? "")
78
+ .filter((text) => text.length > 0)
79
+ .join("\n\n"),
80
+ }),
81
+ ...(withTools.length === 0
82
+ ? {}
83
+ : {
84
+ tools: (ctx) => withTools.flatMap((feature) => [...(feature.tools?.(ctx) ?? [])]),
85
+ }),
86
+ ...(withModelChanged.length === 0
87
+ ? {}
88
+ : {
89
+ modelChanged: (ctx, change) => {
90
+ let injected;
91
+ // Every feature observes the change; the first returned message wins.
92
+ for (const feature of withModelChanged) {
93
+ const message = feature.modelChanged?.(ctx, change);
94
+ injected ??= message;
95
+ }
96
+ return injected;
97
+ },
98
+ }),
99
+ ...spread("beforeAgentLoop", fanOut((feature) => feature.beforeAgentLoop)),
100
+ ...spread("beforeTurn", fanOut((feature) => feature.beforeTurn)),
101
+ ...spread("beforeInference", fanOut((feature) => feature.beforeInference)),
102
+ ...spread("afterInference", fanOut((feature) => feature.afterInference)),
103
+ ...spread("afterTurn", collect((feature) => feature.afterTurn)),
104
+ ...spread("afterAgentLoop", collect((feature) => feature.afterAgentLoop)),
105
+ };
106
+ }
107
+ function fanOutEvent(features) {
108
+ const implemented = features.filter((feature) => feature.onEvent !== undefined);
109
+ if (implemented.length === 0)
110
+ return undefined;
111
+ return (ctx, event) => {
112
+ for (const feature of implemented)
113
+ feature.onEvent?.(ctx, event);
114
+ };
115
+ }
116
+ function spread(key, value) {
117
+ return value === undefined ? {} : { [key]: value };
118
+ }
119
+ //# sourceMappingURL=Agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Agent.js","sourceRoot":"","sources":["../sources/Agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import type { SessionReasoningEffort, SessionServiceTier, SessionUserMessage } from "@slopus/happy-providers";
2
+ import { type Context } from "@steve.kite/stdlib";
3
+ import type { AgentBaseHooks } from "./AgentBaseHooks.js";
4
+ import type { AgentBasePersistence } from "./AgentBasePersistence.js";
5
+ import type { AgentBaseState } from "./AgentBaseState.js";
6
+ import { AgentProviders } from "./AgentProviders.js";
7
+ /** How a message queue drains: one message per model response, or every queued message at once. */
8
+ export type AgentBaseQueueMode = "one-at-a-time" | "all";
9
+ /**
10
+ * Inference settings carried by a queued message. An omitted field keeps the previously
11
+ * effective value; the first message without a value falls back to the constructor default,
12
+ * though relying on that default is discouraged — prefer sending settings with the message.
13
+ */
14
+ export interface AgentBaseMessageOptions {
15
+ /** The registry ID of the provider to switch to. */
16
+ readonly provider?: string;
17
+ readonly model?: string;
18
+ readonly effort?: SessionReasoningEffort;
19
+ readonly serviceTier?: SessionServiceTier;
20
+ }
21
+ export interface AgentBaseOptions {
22
+ /** Stable session identity supplied by the caller. */
23
+ readonly id: string;
24
+ /** The registry providers are resolved from, at session creation time. */
25
+ readonly providers: AgentProviders;
26
+ /** The registry ID of the provider to use; serializable alongside model and effort. */
27
+ readonly provider: string;
28
+ readonly persistence: AgentBasePersistence;
29
+ readonly hooks?: AgentBaseHooks;
30
+ /** Copied into the agent's own mutable `state`. */
31
+ readonly initialState?: Partial<AgentBaseState>;
32
+ readonly model?: string;
33
+ readonly effort?: SessionReasoningEffort;
34
+ readonly serviceTier?: SessionServiceTier;
35
+ readonly steeringMode?: AgentBaseQueueMode;
36
+ readonly sendMode?: AgentBaseQueueMode;
37
+ }
38
+ /**
39
+ * A single agent session over one provider. Messages arrive through two FIFO queues: steering
40
+ * messages inject as soon as the current assistant response and its tool batch finish, while
41
+ * sent messages wait until the agent would otherwise stop — no tool calls or steering remain.
42
+ * Each queue drains per its configured mode, and the conversation is durable through
43
+ * append-only persistence loaded on the first inference attempt.
44
+ */
45
+ export declare class AgentBase {
46
+ #private;
47
+ readonly id: string;
48
+ /**
49
+ * The agent's own copy of the initial state, mutable directly; every inference reads the
50
+ * current values.
51
+ */
52
+ readonly state: AgentBaseState;
53
+ constructor(ctx: Context, options: AgentBaseOptions);
54
+ /**
55
+ * Queue a user message that injects as soon as the current assistant response and its tool
56
+ * batch finish; steering always takes precedence over sent messages. The returned promise
57
+ * resolves once the durable write lands; it waits neither for the history load nor for the
58
+ * turn, and a failed write keeps the message out of the conversation entirely.
59
+ */
60
+ steer(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
61
+ /**
62
+ * Queue a user message that waits until the agent would otherwise stop — no tool calls or
63
+ * steering remain — before injecting. The returned promise resolves once the durable write
64
+ * lands; it waits neither for the history load nor for the turn, and a failed write keeps
65
+ * the message out of the conversation entirely.
66
+ */
67
+ send(ctx: Context, message: SessionUserMessage, options?: AgentBaseMessageOptions): Promise<void>;
68
+ /**
69
+ * Start the loop without a new message: load the durable state and, if a turn was cut off —
70
+ * queued messages, a dispatched tool batch without results, or an unanswered user or tool
71
+ * message — continue it to completion. On an idle history this loads and does nothing more.
72
+ */
73
+ start(): void;
74
+ waitForIdle(): Promise<void>;
75
+ /**
76
+ * Compact the conversation. The compaction waits for the active turn to end — or runs right
77
+ * away when idle — and replaces the compacted history with the provider's replacement
78
+ * context while keeping every message that joined the history after the snapshot. Calls made
79
+ * while a compaction is pending or running await that same compaction; the shared promise
80
+ * resolves when it completes and rejects when the provider reports failure.
81
+ */
82
+ compact(ctx: Context): Promise<void>;
83
+ /**
84
+ * Cancel the active turn: stop consuming the inference stream, settle still-running tool
85
+ * calls as aborted error results, and drop the queued turn request. Blocks that already
86
+ * finished stay in the history; an unfinished block is dropped. Messages still waiting in
87
+ * the steering and send queues stay durable and join the next requested turn. Resolves
88
+ * once the loop has stopped; a no-op when the agent is idle.
89
+ */
90
+ abort(): Promise<void>;
91
+ close(): Promise<void>;
92
+ }
93
+ //# sourceMappingURL=AgentBase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentBase.d.ts","sourceRoot":"","sources":["../sources/AgentBase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAMR,sBAAsB,EACtB,kBAAkB,EAIlB,kBAAkB,EACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAA2C,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAOrD,mGAAmG;AACnG,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG,KAAK,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAC7C;AASD,MAAM,WAAW,gBAAgB;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAChC,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAwC/B,YAAY,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAmBlD;IAWD;;;;;OAKG;IACG,KAAK,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC,CAEf;IAED;;;;;OAKG;IACG,IAAI,CACN,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,IAAI,CAAC,CAEf;IAmBD;;;;OAIG;IACH,KAAK,IAAI,IAAI,CAGZ;IAEK,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjC;IAED;;;;;;OAMG;IACG,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzC;IAyCD;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAM3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAM3B;CAqvBJ"}