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,78 @@
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;
@@ -0,0 +1,119 @@
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
+ /**
17
+ * Thrown when a request arrives at a host that is shutting down or shut down.
18
+ *
19
+ * `close()` lets in-flight work finish and refuses everything after it; this is
20
+ * what "everything after it" receives.
21
+ */
22
+ export class HostClosedError extends Error {
23
+ code = 'ERR_HOST_CLOSED';
24
+ /** Which adapter refused. */
25
+ hostName;
26
+ constructor(hostName) {
27
+ super(`[hosting] the '${hostName}' host is closed and is not accepting new requests. ` +
28
+ `In-flight requests were allowed to finish; this one arrived after close() was called. ` +
29
+ `Serve again on a fresh host to accept new work.`);
30
+ this.name = 'HostClosedError';
31
+ this.hostName = hostName;
32
+ }
33
+ }
34
+ /**
35
+ * Thrown when a request arrives for a session that already has a run in flight
36
+ * and the policy is `'reject'`.
37
+ *
38
+ * The refusal is about the SESSION, not about load: two turns of one
39
+ * conversation racing each other would each answer from the state the other is
40
+ * about to replace. A request for any OTHER session is never refused — it
41
+ * simply waits.
42
+ */
43
+ export class ConcurrentRunError extends Error {
44
+ code = 'ERR_CONCURRENT_RUN';
45
+ /** The session that already has a run going. */
46
+ sessionId;
47
+ /** The run that is already going, when it has announced itself. */
48
+ activeRunId;
49
+ constructor(sessionId, activeRunId) {
50
+ super(`[hosting] session '${sessionId}' already has a run in flight` +
51
+ (activeRunId ? ` (run '${activeRunId}')` : '') +
52
+ `. Refusing rather than running two turns of one conversation at once — ` +
53
+ `they would each answer from state the other is about to replace. ` +
54
+ `Wait for the active run, or build the standing agent with ` +
55
+ `onConcurrentInvoke: 'enqueue' to queue this turn behind it instead.`);
56
+ this.name = 'ConcurrentRunError';
57
+ this.sessionId = sessionId;
58
+ if (activeRunId !== undefined)
59
+ this.activeRunId = activeRunId;
60
+ }
61
+ }
62
+ /**
63
+ * Raised when a run paused to ask a person something and the reply cannot carry
64
+ * a pause.
65
+ *
66
+ * **The run did not fail.** A pause is unfinished work: the agent stopped to ask
67
+ * and is waiting for an answer. What cannot happen here is storing it — the
68
+ * `'conversation-v1'` envelope holds a conversation, and a paused run is a
69
+ * conversation plus an engine checkpoint. So nothing is written, and the
70
+ * session keeps exactly the conversation it had before this request.
71
+ */
72
+ export class PauseNotCarriedError extends Error {
73
+ code = 'ERR_PAUSE_NOT_CARRIED';
74
+ /** The tool that asked, when the run recorded which one it was. */
75
+ toolName;
76
+ /** The session whose stored conversation was left untouched. */
77
+ sessionId;
78
+ constructor(toolName, sessionId) {
79
+ super(`[hosting] the run paused to ask a person about ` +
80
+ (toolName ? `'${toolName}'` : 'a tool') +
81
+ ` and this reply cannot carry a pause. ` +
82
+ `The run did not fail — it is unfinished, waiting on an answer. ` +
83
+ `Nothing was written: ` +
84
+ (sessionId ? `session '${sessionId}'` : 'the session') +
85
+ ` still holds the conversation it held before this request. ` +
86
+ `The 'conversation-v1' envelope stores a conversation; a paused run is a ` +
87
+ `conversation plus an engine checkpoint, and storing half of it would be worse ` +
88
+ `than storing none. Carry the pause yourself with agent.run() / agent.resume().`);
89
+ this.name = 'PauseNotCarriedError';
90
+ if (toolName !== undefined)
91
+ this.toolName = toolName;
92
+ if (sessionId !== undefined)
93
+ this.sessionId = sessionId;
94
+ }
95
+ }
96
+ /**
97
+ * Assert that a host can do something, and throw a corrective error naming the
98
+ * adapter when it cannot.
99
+ *
100
+ * This is the feature-detection law with teeth: capabilities are read, never
101
+ * assumed, and asking for one that is absent tells you which adapter you are
102
+ * actually holding rather than failing quietly somewhere downstream.
103
+ *
104
+ * @example
105
+ * requireCapability(host, 'streaming'); // throws unless this host streams
106
+ *
107
+ * // or branch instead of insisting:
108
+ * if (host.capabilities.includes('streaming')) { ... }
109
+ */
110
+ export function requireCapability(host, capability) {
111
+ if (host.capabilities.includes(capability))
112
+ return;
113
+ const has = host.capabilities.length > 0 ? host.capabilities.join(', ') : 'none';
114
+ throw new Error(`[hosting] the '${host.name}' host does not support '${capability}'. ` +
115
+ `It reports: ${has}. Feature-detect with ` +
116
+ `host.capabilities.includes('${capability}') and fall back, or serve on a host ` +
117
+ `that has it — capabilities are read from the adapter, never assumed from its name.`);
118
+ }
119
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/hosting/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;GAKG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,GAAG,iBAA0B,CAAC;IAC3C,6BAA6B;IACpB,QAAQ,CAAS;IAE1B,YAAY,QAAgB;QAC1B,KAAK,CACH,kBAAkB,QAAQ,sDAAsD;YAC9E,wFAAwF;YACxF,iDAAiD,CACpD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,GAAG,oBAA6B,CAAC;IAC9C,gDAAgD;IACvC,SAAS,CAAS;IAC3B,mEAAmE;IAC1D,WAAW,CAAU;IAE9B,YAAY,SAAiB,EAAE,WAAoB;QACjD,KAAK,CACH,sBAAsB,SAAS,+BAA+B;YAC5D,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,WAAW,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,yEAAyE;YACzE,mEAAmE;YACnE,4DAA4D;YAC5D,qEAAqE,CACxE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChE,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,IAAI,GAAG,uBAAgC,CAAC;IACjD,mEAAmE;IAC1D,QAAQ,CAAU;IAC3B,gEAAgE;IACvD,SAAS,CAAU;IAE5B,YAAY,QAAiB,EAAE,SAAkB;QAC/C,KAAK,CACH,iDAAiD;YAC/C,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YACvC,wCAAwC;YACxC,iEAAiE;YACjE,uBAAuB;YACvB,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,SAAS,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC;YACtD,6DAA6D;YAC7D,0EAA0E;YAC1E,gFAAgF;YAChF,gFAAgF,CACnF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACrD,IAAI,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC1D,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAe,EAAE,UAA0B;IAC3E,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACjF,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,CAAC,IAAI,4BAA4B,UAAU,KAAK;QACpE,eAAe,GAAG,wBAAwB;QAC1C,+BAA+B,UAAU,uCAAuC;QAChF,oFAAoF,CACvF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,53 @@
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';
@@ -0,0 +1,52 @@
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 { memorySessions } from './memorySessions.js';
49
+ export { toEnvelope, readEnvelope } from './envelope.js';
50
+ export { standingAgent } from './standingAgent.js';
51
+ export { requireCapability, HostClosedError, ConcurrentRunError, PauseNotCarriedError, } from './errors.js';
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hosting/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,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"}
@@ -0,0 +1,22 @@
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;
@@ -0,0 +1,31 @@
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
+ /**
15
+ * An in-process session store.
16
+ *
17
+ * @example
18
+ * const sessions = memorySessions();
19
+ * await standingAgent({ agent, sessions, host: nodeHost({ port: 8080 }) });
20
+ */
21
+ export function memorySessions() {
22
+ const stored = new Map();
23
+ return {
24
+ hydrate: (sessionId) => Promise.resolve(stored.get(sessionId)),
25
+ persist: (sessionId, envelope) => {
26
+ stored.set(sessionId, envelope);
27
+ return Promise.resolve();
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=memorySessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memorySessions.js","sourceRoot":"","sources":["../../../src/hosting/memorySessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;IACrD,OAAO;QACL,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9D,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAChC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,64 @@
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;