agentfootprint 7.13.0 → 7.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/core/Agent.js +79 -2
  2. package/dist/core/Agent.js.map +1 -1
  3. package/dist/esm/core/Agent.d.ts +47 -0
  4. package/dist/esm/core/Agent.js +79 -2
  5. package/dist/esm/core/Agent.js.map +1 -1
  6. package/dist/esm/hosting/envelope.d.ts +33 -0
  7. package/dist/esm/hosting/envelope.js +49 -0
  8. package/dist/esm/hosting/envelope.js.map +1 -0
  9. package/dist/esm/hosting/errors.d.ts +78 -0
  10. package/dist/esm/hosting/errors.js +119 -0
  11. package/dist/esm/hosting/errors.js.map +1 -0
  12. package/dist/esm/hosting/index.d.ts +53 -0
  13. package/dist/esm/hosting/index.js +52 -0
  14. package/dist/esm/hosting/index.js.map +1 -0
  15. package/dist/esm/hosting/memorySessions.d.ts +22 -0
  16. package/dist/esm/hosting/memorySessions.js +31 -0
  17. package/dist/esm/hosting/memorySessions.js.map +1 -0
  18. package/dist/esm/hosting/nodeHost.d.ts +64 -0
  19. package/dist/esm/hosting/nodeHost.js +245 -0
  20. package/dist/esm/hosting/nodeHost.js.map +1 -0
  21. package/dist/esm/hosting/standingAgent.d.ts +62 -0
  22. package/dist/esm/hosting/standingAgent.js +192 -0
  23. package/dist/esm/hosting/standingAgent.js.map +1 -0
  24. package/dist/esm/hosting/types.d.ts +206 -0
  25. package/dist/esm/hosting/types.js +21 -0
  26. package/dist/esm/hosting/types.js.map +1 -0
  27. package/dist/hosting/envelope.js +54 -0
  28. package/dist/hosting/envelope.js.map +1 -0
  29. package/dist/hosting/errors.js +126 -0
  30. package/dist/hosting/errors.js.map +1 -0
  31. package/dist/hosting/index.js +64 -0
  32. package/dist/hosting/index.js.map +1 -0
  33. package/dist/hosting/memorySessions.js +35 -0
  34. package/dist/hosting/memorySessions.js.map +1 -0
  35. package/dist/hosting/nodeHost.js +272 -0
  36. package/dist/hosting/nodeHost.js.map +1 -0
  37. package/dist/hosting/standingAgent.js +196 -0
  38. package/dist/hosting/standingAgent.js.map +1 -0
  39. package/dist/hosting/types.js +22 -0
  40. package/dist/hosting/types.js.map +1 -0
  41. package/dist/types/core/Agent.d.ts +47 -0
  42. package/dist/types/core/Agent.d.ts.map +1 -1
  43. package/dist/types/hosting/envelope.d.ts +34 -0
  44. package/dist/types/hosting/envelope.d.ts.map +1 -0
  45. package/dist/types/hosting/errors.d.ts +79 -0
  46. package/dist/types/hosting/errors.d.ts.map +1 -0
  47. package/dist/types/hosting/index.d.ts +54 -0
  48. package/dist/types/hosting/index.d.ts.map +1 -0
  49. package/dist/types/hosting/memorySessions.d.ts +23 -0
  50. package/dist/types/hosting/memorySessions.d.ts.map +1 -0
  51. package/dist/types/hosting/nodeHost.d.ts +65 -0
  52. package/dist/types/hosting/nodeHost.d.ts.map +1 -0
  53. package/dist/types/hosting/standingAgent.d.ts +63 -0
  54. package/dist/types/hosting/standingAgent.d.ts.map +1 -0
  55. package/dist/types/hosting/types.d.ts +207 -0
  56. package/dist/types/hosting/types.d.ts.map +1 -0
  57. package/package.json +14 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * hosting/errors — the refusals, authored once so every adapter refuses in the
3
+ * same words.
4
+ *
5
+ * A refusal that varies by adapter is a refusal nobody can write a test or a
6
+ * runbook against. These three carry a stable `code`, name WHO refused, and say
7
+ * what the caller should do instead. Adapters map the codes onto whatever their
8
+ * transport uses to say "no" — that mapping is the adapter's business and lives
9
+ * in the adapter, never here.
10
+ *
11
+ * `requireCapability` is the fourth refusal and the only one that is a
12
+ * programming mistake rather than a runtime condition, so it throws a plain
13
+ * `Error`: nothing branches on "I forgot to feature-detect", it just needs to
14
+ * say so loudly and name the adapter it is talking about.
15
+ */
16
+ import type { AgentHost, HostCapability } from './types.js';
17
+ /**
18
+ * Thrown when a request arrives at a host that is shutting down or shut down.
19
+ *
20
+ * `close()` lets in-flight work finish and refuses everything after it; this is
21
+ * what "everything after it" receives.
22
+ */
23
+ export declare class HostClosedError extends Error {
24
+ readonly code: "ERR_HOST_CLOSED";
25
+ /** Which adapter refused. */
26
+ readonly hostName: string;
27
+ constructor(hostName: string);
28
+ }
29
+ /**
30
+ * Thrown when a request arrives for a session that already has a run in flight
31
+ * and the policy is `'reject'`.
32
+ *
33
+ * The refusal is about the SESSION, not about load: two turns of one
34
+ * conversation racing each other would each answer from the state the other is
35
+ * about to replace. A request for any OTHER session is never refused — it
36
+ * simply waits.
37
+ */
38
+ export declare class ConcurrentRunError extends Error {
39
+ readonly code: "ERR_CONCURRENT_RUN";
40
+ /** The session that already has a run going. */
41
+ readonly sessionId: string;
42
+ /** The run that is already going, when it has announced itself. */
43
+ readonly activeRunId?: string;
44
+ constructor(sessionId: string, activeRunId?: string);
45
+ }
46
+ /**
47
+ * Raised when a run paused to ask a person something and the reply cannot carry
48
+ * a pause.
49
+ *
50
+ * **The run did not fail.** A pause is unfinished work: the agent stopped to ask
51
+ * and is waiting for an answer. What cannot happen here is storing it — the
52
+ * `'conversation-v1'` envelope holds a conversation, and a paused run is a
53
+ * conversation plus an engine checkpoint. So nothing is written, and the
54
+ * session keeps exactly the conversation it had before this request.
55
+ */
56
+ export declare class PauseNotCarriedError extends Error {
57
+ readonly code: "ERR_PAUSE_NOT_CARRIED";
58
+ /** The tool that asked, when the run recorded which one it was. */
59
+ readonly toolName?: string;
60
+ /** The session whose stored conversation was left untouched. */
61
+ readonly sessionId?: string;
62
+ constructor(toolName?: string, sessionId?: string);
63
+ }
64
+ /**
65
+ * Assert that a host can do something, and throw a corrective error naming the
66
+ * adapter when it cannot.
67
+ *
68
+ * This is the feature-detection law with teeth: capabilities are read, never
69
+ * assumed, and asking for one that is absent tells you which adapter you are
70
+ * actually holding rather than failing quietly somewhere downstream.
71
+ *
72
+ * @example
73
+ * requireCapability(host, 'streaming'); // throws unless this host streams
74
+ *
75
+ * // or branch instead of insisting:
76
+ * if (host.capabilities.includes('streaming')) { ... }
77
+ */
78
+ export declare function requireCapability(host: AgentHost, capability: HostCapability): void;
79
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/hosting/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5D;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,oBAA8B;IAC3C,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM;CAS7B;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,uBAAiC;IAC9C,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAElB,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM;CAapD;AAED;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,0BAAoC;IACjD,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAiBlD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI,CASnF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * agentfootprint/hosting — the two ports between an agent and the place it runs.
3
+ *
4
+ * An agent that answers one call in a script and an agent that has been up for
5
+ * a month differ in two things, and only two: something has to carry requests
6
+ * to it, and the conversation has to outlive the request. This subpath is those
7
+ * two things as **ports**, plus local adapters that prove the ports work, plus
8
+ * the composer that wires them together.
9
+ *
10
+ * `AgentHost` — something can call me.
11
+ * `SessionLifecycle` — the conversation outlives the request.
12
+ * `standingAgent` — hydrate → resume-or-fresh → persist → reply.
13
+ *
14
+ * ── Why the ports look like nothing in particular ────────────────────────────
15
+ * Deliberately. Not one field, name or assumption here comes from any hosting
16
+ * product, cloud or protocol. A port shaped around one provider's request
17
+ * envelope stops being a port and becomes that provider's SDK with extra steps,
18
+ * and every adapter after the first pays for the shortcut. So the ports carry
19
+ * an input, a reply, an optional session id, headers, a signal — the vocabulary
20
+ * every transport already has — and everything specific to one place you might
21
+ * deploy lives in the adapter for that place. `nodeHost` gets no special
22
+ * treatment: its paths, status codes and JSON body shape are all in
23
+ * `nodeHost.ts` and the port types do not know they exist. A test greps these
24
+ * source files for vendor names, crudely and on purpose.
25
+ *
26
+ * ── What ships here ──────────────────────────────────────────────────────────
27
+ * • `nodeHost({ port?, hostname?, invokePath?, healthPath? })` — plain
28
+ * `node:http`, zero dependencies. `POST /invoke`, `GET /health`, and
29
+ * Server-Sent Events when the caller asks for them.
30
+ * • `memorySessions()` — conversations in a Map, for tests and local dev.
31
+ * • `standingAgent({ agent, sessions, host })` — the composer.
32
+ * • `toEnvelope` / `readEnvelope` — pack a conversation, and refuse by name to
33
+ * unpack a format this runtime does not know.
34
+ * • `requireCapability` — feature-detection with teeth.
35
+ *
36
+ * @example An agent that stays up and remembers
37
+ * import { Agent } from 'agentfootprint';
38
+ * import { standingAgent, nodeHost, memorySessions } from 'agentfootprint/hosting';
39
+ *
40
+ * const handle = await standingAgent({
41
+ * agent: Agent.create({ provider, model }).system('You help customers.').build(),
42
+ * sessions: memorySessions(),
43
+ * host: nodeHost({ port: 8080 }),
44
+ * });
45
+ * process.on('SIGTERM', () => void handle.close());
46
+ */
47
+ export { nodeHost } from './nodeHost.js';
48
+ export type { NodeHost, NodeHostHandle, NodeHostOptions } from './nodeHost.js';
49
+ export { memorySessions } from './memorySessions.js';
50
+ export { toEnvelope, readEnvelope } from './envelope.js';
51
+ export { standingAgent } from './standingAgent.js';
52
+ export { requireCapability, HostClosedError, ConcurrentRunError, PauseNotCarriedError, } from './errors.js';
53
+ export type { AgentHost, CheckpointEnvelope, ConcurrentInvokePolicy, HostCapability, HostHandle, HostHandler, HostReply, HostRequest, SessionLifecycle, StandingAgentOptions, WakeReason, } from './types.js';
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hosting/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,sBAAsB,EACtB,cAAc,EACd,UAAU,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,GACX,MAAM,YAAY,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * hosting/memorySessions — conversations in a Map.
3
+ *
4
+ * The smallest thing that satisfies `SessionLifecycle`, for tests and for local
5
+ * development where a Redis is ceremony you have not earned yet. It is exactly
6
+ * as durable as the process: restart and every conversation is gone.
7
+ *
8
+ * That is not a shortcoming to apologise for, it is the point of the port. Swap
9
+ * this for a store that survives a restart and the standing agent is unchanged
10
+ * — which is also how the "a crashed process resumes the conversation" test is
11
+ * written: keep the store, throw away everything else, and watch the
12
+ * conversation come back through the envelope alone.
13
+ */
14
+ import type { SessionLifecycle } from './types.js';
15
+ /**
16
+ * An in-process session store.
17
+ *
18
+ * @example
19
+ * const sessions = memorySessions();
20
+ * await standingAgent({ agent, sessions, host: nodeHost({ port: 8080 }) });
21
+ */
22
+ export declare function memorySessions(): SessionLifecycle;
23
+ //# sourceMappingURL=memorySessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memorySessions.d.ts","sourceRoot":"","sources":["../../../src/hosting/memorySessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,gBAAgB,CASjD"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * hosting/nodeHost — the plain HTTP adapter, built on `node:http` and nothing
3
+ * else.
4
+ *
5
+ * const host = nodeHost({ port: 8080 });
6
+ * const handle = await host.serve(async (request, reply) => {
7
+ * reply.complete(await answer(request.input));
8
+ * });
9
+ *
10
+ * Two routes: `POST /invoke` takes `{ input, sessionId? }` and answers
11
+ * `{ output }`; `GET /health` answers `{ status: 'ok' }`. Both paths are
12
+ * options, because the paths are the part most likely to be dictated to you by
13
+ * whatever is in front of the process — a load balancer, a container contract,
14
+ * a colleague's convention. A path is a deployment detail, so it is a knob
15
+ * here and absent from the port entirely.
16
+ *
17
+ * **Streaming is the caller's choice, not the server's.** Send
18
+ * `Accept: text/event-stream` and the reply is Server-Sent Events, one `chunk`
19
+ * event per `reply.emit(...)` then a final `complete`. Send anything else and
20
+ * the same handler produces one JSON body — it emits into a buffer that the
21
+ * completion settles. The handler cannot tell the difference and does not need
22
+ * to, which is the property `capabilities` exists to describe.
23
+ *
24
+ * Pattern: Adapter. Everything specific to HTTP — the paths, the JSON body
25
+ * shape, the status codes, the SSE framing — lives in this file. `types.ts`
26
+ * knows none of it, and a future adapter for somewhere else re-decides all of
27
+ * it without touching a port.
28
+ */
29
+ import type { AgentHost, HostHandle, HostHandler } from './types.js';
30
+ /** Options for {@link nodeHost}. */
31
+ export interface NodeHostOptions {
32
+ /** Port to bind. Default `8080`. Pass `0` for an ephemeral port. */
33
+ readonly port?: number;
34
+ /** Interface to bind. Default `'0.0.0.0'`. */
35
+ readonly hostname?: string;
36
+ /** Path that takes a request. Default `'/invoke'`. */
37
+ readonly invokePath?: string;
38
+ /** Path that answers a health probe. Default `'/health'`. */
39
+ readonly healthPath?: string;
40
+ }
41
+ /** What {@link nodeHost}'s `serve()` resolves to — a {@link HostHandle} that also says where it landed. */
42
+ export interface NodeHostHandle extends HostHandle {
43
+ /** Where it is actually listening, e.g. `http://127.0.0.1:53211`. */
44
+ readonly url: string;
45
+ /** The port it actually bound — the real one, when you asked for `0`. */
46
+ readonly port: number;
47
+ }
48
+ /** {@link AgentHost} narrowed to this adapter's handle. */
49
+ export interface NodeHost extends AgentHost {
50
+ serve(handler: HostHandler): Promise<NodeHostHandle>;
51
+ }
52
+ /**
53
+ * An HTTP host for one handler.
54
+ *
55
+ * @example
56
+ * const handle = await nodeHost({ port: 0 }).serve(handler);
57
+ * await fetch(`${handle.url}/invoke`, {
58
+ * method: 'POST',
59
+ * headers: { 'content-type': 'application/json' },
60
+ * body: JSON.stringify({ input: 'hello', sessionId: 'c-1' }),
61
+ * });
62
+ * await handle.close();
63
+ */
64
+ export declare function nodeHost(options?: NodeHostOptions): NodeHost;
65
+ //# sourceMappingURL=nodeHost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeHost.d.ts","sourceRoot":"","sources":["../../../src/hosting/nodeHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAkB,UAAU,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAEhG,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,2GAA2G;AAC3G,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,2DAA2D;AAC3D,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACtD;AAoBD;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAqEhE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * hosting/standingAgent — an agent that stays up and remembers.
3
+ *
4
+ * await standingAgent({
5
+ * agent,
6
+ * sessions: memorySessions(),
7
+ * host: nodeHost({ port: 8080 }),
8
+ * });
9
+ *
10
+ * One request at a time it does four things: wake and hydrate the session,
11
+ * resume that conversation or start a fresh one, persist what the run leaves
12
+ * behind, then reply. Everything else is somebody else's job — the host carries
13
+ * bytes, the store keeps them, the agent thinks.
14
+ *
15
+ * ── Resuming is a REPLAY, and that has a cost you must know about ────────────
16
+ * A stored conversation is restored through `agent.resumeOnError(...)`, and
17
+ * this is its caveat, stated here in the words the Agent states it in, because
18
+ * a composition that hides the caveat of the thing it composes is worse than no
19
+ * composition at all:
20
+ *
21
+ * > **Tool re-execution / idempotency**: tool side effects from the FAILED
22
+ * > iteration are not in the checkpoint. The model re-decides from the
23
+ * > restored history and may re-issue those tool calls — they WILL execute
24
+ * > again (there is no built-in toolCallId dedup). Mutating tools (payments,
25
+ * > emails, DB writes) must be idempotent — key on stable call content, not
26
+ * > `ctx.toolCallId` (a re-issued call gets a new id).
27
+ *
28
+ * ── Why one run at a time ───────────────────────────────────────────────────
29
+ * An Agent instance holds per-run state on itself, and this composer shares ONE
30
+ * instance across every session. Two runs overlapping on it do not crash —
31
+ * which is precisely the danger. They both finish, and the state the composer
32
+ * reads afterwards belongs to whichever started last, so one session's envelope
33
+ * can end up holding another session's conversation. Nothing in the recording
34
+ * would say so. Runs are therefore serialized, globally, and that is a
35
+ * correctness requirement rather than a tuning choice.
36
+ *
37
+ * `onConcurrentInvoke` is the separate question of what to do when a second
38
+ * turn of the SAME conversation arrives while the first is running: refuse it
39
+ * (default) or queue it. A request for a DIFFERENT session is never refused —
40
+ * there is nothing wrong with it; it simply waits its turn.
41
+ *
42
+ * Pattern: Composition root. It owns wiring and ordering; it invents no
43
+ * mechanism of its own.
44
+ */
45
+ import type { HostHandle, StandingAgentOptions } from './types.js';
46
+ /**
47
+ * Serve one agent, with per-session conversation memory, on any
48
+ * {@link AgentHost}.
49
+ *
50
+ * Resolves once the host is live. Closing the returned handle closes the host
51
+ * and detaches the listeners this composer added to the agent.
52
+ *
53
+ * @example
54
+ * const handle = await standingAgent({
55
+ * agent,
56
+ * sessions: memorySessions(),
57
+ * host: nodeHost({ port: 0 }),
58
+ * onConcurrentInvoke: 'enqueue',
59
+ * });
60
+ * process.on('SIGTERM', () => void handle.close());
61
+ */
62
+ export declare function standingAgent<TH extends HostHandle>(options: StandingAgentOptions<TH>): Promise<TH>;
63
+ //# sourceMappingURL=standingAgent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standingAgent.d.ts","sourceRoot":"","sources":["../../../src/hosting/standingAgent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAMH,OAAO,KAAK,EACV,UAAU,EAGV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAGpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,EAAE,SAAS,UAAU,EACvD,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAC,GAChC,OAAO,CAAC,EAAE,CAAC,CAuHb"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * hosting/types — the two ports an agent needs to stand up and stay up.
3
+ *
4
+ * `AgentHost` is "something can call me". `SessionLifecycle` is "the
5
+ * conversation outlives the request". Both are deliberately written in the
6
+ * vocabulary every transport and every store already has — an input, a reply,
7
+ * a session id, a stored blob — and in nothing else.
8
+ *
9
+ * **The rule these types are written under:** no runtime, product or protocol
10
+ * gets a field, a name or an assumption here. A port shaped around one
11
+ * provider's request envelope stops being a port and becomes that provider's
12
+ * SDK with extra steps, and every later adapter pays for it. If a decision only
13
+ * makes sense for one place you might deploy, it belongs in the adapter for
14
+ * that place, not in this file. `nodeHost` is the first adapter and it does not
15
+ * get special treatment either: its paths, its status codes and its JSON body
16
+ * shape all live in `nodeHost.ts`, and nothing in this file knows they exist.
17
+ *
18
+ * Pattern: Ports & adapters (hexagonal). Role: the port side, exclusively.
19
+ */
20
+ import type { Agent } from '../core/Agent.js';
21
+ import type { AgentRunCheckpoint } from '../core/runCheckpoint.js';
22
+ /**
23
+ * Something a host can do BEYOND the baseline of "accept a request, deliver one
24
+ * reply". Read it from {@link AgentHost.capabilities} and branch on it — never
25
+ * assume it, and never infer it from the adapter's name.
26
+ *
27
+ * The union starts at exactly what a shipped adapter can honour today. A name
28
+ * is added when an adapter can actually keep the promise, never in anticipation
29
+ * of a transport that does not exist yet: a capability nobody implements is a
30
+ * promise the library cannot keep, and pre-minting one for an imagined future
31
+ * transport would bake that transport's assumptions in before it arrives.
32
+ */
33
+ export type HostCapability = 'streaming';
34
+ /**
35
+ * One inbound request, as the transport described it.
36
+ */
37
+ export interface HostRequest {
38
+ /** What the caller is asking. */
39
+ readonly input: string;
40
+ /**
41
+ * The conversation this request CLAIMS to belong to — caller data, exactly as
42
+ * the transport declared it (a JSON field, a header, a path segment).
43
+ *
44
+ * It is **not identity** and must never be trusted as identity on its own:
45
+ * anyone who can reach the host can put any string here, including someone
46
+ * else's. Authenticate the caller by your own means, then check that the
47
+ * authenticated principal is allowed this session, before you serve it.
48
+ */
49
+ readonly sessionId?: string;
50
+ /**
51
+ * Transport headers with lower-cased names, as delivered. Present so a
52
+ * handler can map its own conventions (a correlation id, a tenant) without
53
+ * the port having to guess which ones matter.
54
+ */
55
+ readonly headers?: Readonly<Record<string, string>>;
56
+ /** Aborted when the caller goes away. */
57
+ readonly signal?: AbortSignal;
58
+ }
59
+ /**
60
+ * The one reply a request gets. Exactly one of {@link HostReply.complete} or
61
+ * {@link HostReply.fail} ends it; a second call is ignored rather than allowed
62
+ * to corrupt the wire.
63
+ */
64
+ export interface HostReply {
65
+ /** Deliver the final answer and end the reply. */
66
+ complete(output: string): void;
67
+ /**
68
+ * A piece of the answer, as it is produced.
69
+ *
70
+ * Optional on the TYPE so a minimal adapter need not implement it — every
71
+ * shipped adapter does. Whether the caller SEES the pieces as they arrive is
72
+ * the whole difference between hosts, and that is what `'streaming'` in
73
+ * {@link AgentHost.capabilities} reports. A host without it buffers what it is
74
+ * handed and the authoritative `complete(output)` is what the caller
75
+ * receives; the buffer is settled by the completion, never sent alongside it,
76
+ * because a chunk is a preview of the same text and delivering both would
77
+ * hand the caller the answer twice.
78
+ *
79
+ * Handler code is identical either way: emit freely, complete once.
80
+ */
81
+ emit?(chunk: string): void;
82
+ /** End the reply with a failure. */
83
+ fail(error: Error): void;
84
+ }
85
+ /**
86
+ * What you hand {@link AgentHost.serve}. Throwing is treated exactly like
87
+ * calling `reply.fail(err)` — a handler that throws is a failed request, never
88
+ * a hung one.
89
+ */
90
+ export type HostHandler = (request: HostRequest, reply: HostReply) => void | Promise<void>;
91
+ /** A live host. */
92
+ export interface HostHandle {
93
+ /**
94
+ * Stop taking new requests, let the in-flight ones finish, then release the
95
+ * transport. Idempotent, so a shutdown hook and an explicit close can
96
+ * coexist. Requests arriving after it are refused with a
97
+ * {@link HostClosedError} naming the adapter.
98
+ */
99
+ close(): Promise<void>;
100
+ }
101
+ /**
102
+ * The port: something that can carry requests to one handler and carry its
103
+ * replies back.
104
+ */
105
+ export interface AgentHost {
106
+ /**
107
+ * Which adapter this is. Every refusal names it, so an error tells you WHO
108
+ * refused rather than leaving you to guess which layer you are looking at.
109
+ */
110
+ readonly name: string;
111
+ /** What this adapter can do beyond the baseline. Feature-detect; never assume. */
112
+ readonly capabilities: readonly HostCapability[];
113
+ /** Start serving. Resolves once the host is actually live. */
114
+ serve(handler: HostHandler): Promise<HostHandle>;
115
+ }
116
+ /**
117
+ * A conversation packed for storage.
118
+ *
119
+ * `format` names WHAT is inside, so a reader that does not know the shape
120
+ * refuses BY NAME instead of restoring a conversation it cannot actually read.
121
+ * Formats are ADDED, never redefined: an old runtime meeting a new format says
122
+ * so and stops, which is the only safe thing it can do with a payload it cannot
123
+ * interpret.
124
+ *
125
+ * `'conversation-v1'` stores a conversation and only a conversation. A run that
126
+ * paused mid-flow is a conversation PLUS an engine checkpoint, and this format
127
+ * has nowhere to put the second half — which is why `standingAgent` refuses to
128
+ * store a paused run rather than storing half of it. Carrying a pause would be
129
+ * a NEW format name in this same envelope, read by a runtime that knows it and
130
+ * refused by name everywhere else. That is what the version field is for.
131
+ */
132
+ export interface CheckpointEnvelope {
133
+ /** Names the shape of `data`. Unknown values are refused, never guessed at. */
134
+ readonly format: 'conversation-v1';
135
+ /** The conversation itself — an `AgentRunCheckpoint` for `'conversation-v1'`. */
136
+ readonly data: AgentRunCheckpoint;
137
+ /** Wall-clock when it was packed. Diagnostic. */
138
+ readonly savedAt: number;
139
+ }
140
+ /**
141
+ * Why a session is being woken.
142
+ *
143
+ * One member, because one thing in this release can actually fire it: a request
144
+ * arrived for that session. Naming reasons nothing can produce would be an
145
+ * interface describing a system that does not exist.
146
+ */
147
+ export type WakeReason = 'invoke';
148
+ /**
149
+ * The port: where a conversation lives between requests.
150
+ *
151
+ * Deliberately two required methods. Anything a real store also wants — a TTL,
152
+ * a scan, a delete — is that store's own API, not a demand this port makes of
153
+ * every store that will ever implement it.
154
+ */
155
+ export interface SessionLifecycle {
156
+ /** The stored conversation, or `undefined` for a session that has none yet. */
157
+ hydrate(sessionId: string): Promise<CheckpointEnvelope | undefined>;
158
+ /** Store the conversation for this session. Last write wins. */
159
+ persist(sessionId: string, envelope: CheckpointEnvelope): Promise<void>;
160
+ /**
161
+ * Called once per served request, before `hydrate`, for stores that need to
162
+ * spin something up before they can answer. Errors from it fail the request —
163
+ * a store that could not wake cannot be read from either.
164
+ */
165
+ onWake?(sessionId: string, reason: WakeReason): void | Promise<void>;
166
+ }
167
+ /**
168
+ * What to do when a request arrives for a session that already has a run in
169
+ * flight.
170
+ *
171
+ * - `'reject'` (default) — refuse it, naming the run that is already going.
172
+ * A user who double-submits gets one answer and one refusal, not two runs
173
+ * racing to write the same conversation.
174
+ * - `'enqueue'` — queue it. It starts after the active run has persisted, so
175
+ * the second turn sees the first turn's stored state rather than the state
176
+ * it was about to overwrite.
177
+ *
178
+ * This governs the SAME session only. A request for a different session is
179
+ * never refused — there is nothing wrong with it; it simply waits its turn.
180
+ */
181
+ export type ConcurrentInvokePolicy = 'reject' | 'enqueue';
182
+ /**
183
+ * Options for {@link standingAgent}.
184
+ *
185
+ * Generic in the host's own handle type so composing does not cost you what
186
+ * the adapter told you. `nodeHost` hands back the URL it actually bound —
187
+ * which is the only way to find out when you asked for port `0` — and passing
188
+ * it through `standingAgent` keeps that, without the port having to know that
189
+ * "a URL" is a thing some adapters have.
190
+ */
191
+ export interface StandingAgentOptions<TH extends HostHandle = HostHandle> {
192
+ /**
193
+ * The agent that answers. ONE instance, shared by every session — which is
194
+ * why the composer runs one request at a time (see
195
+ * {@link ConcurrentInvokePolicy}).
196
+ */
197
+ readonly agent: Agent;
198
+ /** Where conversations live between requests. */
199
+ readonly sessions: SessionLifecycle;
200
+ /** What carries requests in. */
201
+ readonly host: AgentHost & {
202
+ serve(handler: HostHandler): Promise<TH>;
203
+ };
204
+ /** Default `'reject'`. See {@link ConcurrentInvokePolicy}. */
205
+ readonly onConcurrentInvoke?: ConcurrentInvokePolicy;
206
+ }
207
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hosting/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,yCAAyC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,oCAAoC;IACpC,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3F,mBAAmB;AACnB,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,SAAS,cAAc,EAAE,CAAC;IACjD,8DAA8D;IAC9D,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD;AAID;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IACpE,gEAAgE;IAChE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAID;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB,CAAC,EAAE,SAAS,UAAU,GAAG,UAAU;IACtE;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG;QAAE,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,EAAE,CAAC,CAAA;KAAE,CAAC;IACxE,8DAA8D;IAC9D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CACtD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentfootprint",
3
- "version": "7.13.0",
3
+ "version": "7.14.0",
4
4
  "description": "The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",
@@ -276,6 +276,16 @@
276
276
  "default": "./dist/identity.js"
277
277
  }
278
278
  },
279
+ "./hosting": {
280
+ "import": {
281
+ "types": "./dist/esm/hosting/index.d.ts",
282
+ "default": "./dist/esm/hosting/index.js"
283
+ },
284
+ "require": {
285
+ "types": "./dist/types/hosting/index.d.ts",
286
+ "default": "./dist/hosting/index.js"
287
+ }
288
+ },
279
289
  "./reliability": {
280
290
  "import": {
281
291
  "types": "./dist/esm/reliability/index.d.ts",
@@ -381,6 +391,9 @@
381
391
  "identity": [
382
392
  "./dist/types/identity.d.ts"
383
393
  ],
394
+ "hosting": [
395
+ "./dist/types/hosting/index.d.ts"
396
+ ],
384
397
  "reliability": [
385
398
  "./dist/types/reliability/index.d.ts"
386
399
  ],