agentfootprint 7.13.0 → 7.15.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.
- package/dist/adapters/hosting/agentcore.js +350 -0
- package/dist/adapters/hosting/agentcore.js.map +1 -0
- package/dist/adapters/memory/agentcore.js +125 -1
- package/dist/adapters/memory/agentcore.js.map +1 -1
- package/dist/adapters/security/agentcore.js +0 -0
- package/dist/adapters/security/agentcore.js.map +1 -0
- package/dist/core/Agent.js +79 -2
- package/dist/core/Agent.js.map +1 -1
- package/dist/esm/adapters/hosting/agentcore.d.ts +199 -0
- package/dist/esm/adapters/hosting/agentcore.js +321 -0
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -0
- package/dist/esm/adapters/memory/agentcore.d.ts +88 -2
- package/dist/esm/adapters/memory/agentcore.js +125 -1
- package/dist/esm/adapters/memory/agentcore.js.map +1 -1
- package/dist/esm/adapters/security/agentcore.d.ts +157 -0
- package/dist/esm/adapters/security/agentcore.js +0 -0
- package/dist/esm/adapters/security/agentcore.js.map +1 -0
- package/dist/esm/core/Agent.d.ts +47 -0
- package/dist/esm/core/Agent.js +79 -2
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/hosting/envelope.d.ts +33 -0
- package/dist/esm/hosting/envelope.js +49 -0
- package/dist/esm/hosting/envelope.js.map +1 -0
- package/dist/esm/hosting/errors.d.ts +78 -0
- package/dist/esm/hosting/errors.js +119 -0
- package/dist/esm/hosting/errors.js.map +1 -0
- package/dist/esm/hosting/httpHost.d.ts +121 -0
- package/dist/esm/hosting/httpHost.js +248 -0
- package/dist/esm/hosting/httpHost.js.map +1 -0
- package/dist/esm/hosting/index.d.ts +60 -0
- package/dist/esm/hosting/index.js +58 -0
- package/dist/esm/hosting/index.js.map +1 -0
- package/dist/esm/hosting/memorySessions.d.ts +22 -0
- package/dist/esm/hosting/memorySessions.js +31 -0
- package/dist/esm/hosting/memorySessions.js.map +1 -0
- package/dist/esm/hosting/nodeHost.d.ts +71 -0
- package/dist/esm/hosting/nodeHost.js +78 -0
- package/dist/esm/hosting/nodeHost.js.map +1 -0
- package/dist/esm/hosting/standingAgent.d.ts +62 -0
- package/dist/esm/hosting/standingAgent.js +192 -0
- package/dist/esm/hosting/standingAgent.js.map +1 -0
- package/dist/esm/hosting/types.d.ts +206 -0
- package/dist/esm/hosting/types.js +21 -0
- package/dist/esm/hosting/types.js.map +1 -0
- package/dist/esm/hosting-providers.d.ts +50 -0
- package/dist/esm/hosting-providers.js +50 -0
- package/dist/esm/hosting-providers.js.map +1 -0
- package/dist/esm/lib/mcp/gatewayTransport.d.ts +103 -0
- package/dist/esm/lib/mcp/gatewayTransport.js +123 -0
- package/dist/esm/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/esm/lib/mcp/index.d.ts +2 -1
- package/dist/esm/lib/mcp/index.js +1 -0
- package/dist/esm/lib/mcp/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpClient.js +10 -2
- package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
- package/dist/esm/lib/mcp/types.d.ts +45 -1
- package/dist/esm/memory/store/types.d.ts +22 -0
- package/dist/esm/memory-providers.d.ts +1 -1
- package/dist/esm/memory-providers.js.map +1 -1
- package/dist/esm/security/index.d.ts +10 -1
- package/dist/esm/security/index.js +14 -1
- package/dist/esm/security/index.js.map +1 -1
- package/dist/esm/tool-providers/index.d.ts +6 -2
- package/dist/esm/tool-providers/index.js +5 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/hosting/envelope.js +54 -0
- package/dist/hosting/envelope.js.map +1 -0
- package/dist/hosting/errors.js +126 -0
- package/dist/hosting/errors.js.map +1 -0
- package/dist/hosting/httpHost.js +276 -0
- package/dist/hosting/httpHost.js.map +1 -0
- package/dist/hosting/index.js +73 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/hosting/memorySessions.js +35 -0
- package/dist/hosting/memorySessions.js.map +1 -0
- package/dist/hosting/nodeHost.js +82 -0
- package/dist/hosting/nodeHost.js.map +1 -0
- package/dist/hosting/standingAgent.js +196 -0
- package/dist/hosting/standingAgent.js.map +1 -0
- package/dist/hosting/types.js +22 -0
- package/dist/hosting/types.js.map +1 -0
- package/dist/hosting-providers.js +57 -0
- package/dist/hosting-providers.js.map +1 -0
- package/dist/lib/mcp/gatewayTransport.js +129 -0
- package/dist/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/lib/mcp/index.js +4 -1
- package/dist/lib/mcp/index.js.map +1 -1
- package/dist/lib/mcp/mcpClient.js +10 -2
- package/dist/lib/mcp/mcpClient.js.map +1 -1
- package/dist/memory-providers.js.map +1 -1
- package/dist/security/index.js +16 -2
- package/dist/security/index.js.map +1 -1
- package/dist/tool-providers/index.js +7 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +200 -0
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -0
- package/dist/types/adapters/memory/agentcore.d.ts +88 -2
- package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/security/agentcore.d.ts +158 -0
- package/dist/types/adapters/security/agentcore.d.ts.map +1 -0
- package/dist/types/core/Agent.d.ts +47 -0
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/hosting/envelope.d.ts +34 -0
- package/dist/types/hosting/envelope.d.ts.map +1 -0
- package/dist/types/hosting/errors.d.ts +79 -0
- package/dist/types/hosting/errors.d.ts.map +1 -0
- package/dist/types/hosting/httpHost.d.ts +122 -0
- package/dist/types/hosting/httpHost.d.ts.map +1 -0
- package/dist/types/hosting/index.d.ts +61 -0
- package/dist/types/hosting/index.d.ts.map +1 -0
- package/dist/types/hosting/memorySessions.d.ts +23 -0
- package/dist/types/hosting/memorySessions.d.ts.map +1 -0
- package/dist/types/hosting/nodeHost.d.ts +72 -0
- package/dist/types/hosting/nodeHost.d.ts.map +1 -0
- package/dist/types/hosting/standingAgent.d.ts +63 -0
- package/dist/types/hosting/standingAgent.d.ts.map +1 -0
- package/dist/types/hosting/types.d.ts +207 -0
- package/dist/types/hosting/types.d.ts.map +1 -0
- package/dist/types/hosting-providers.d.ts +51 -0
- package/dist/types/hosting-providers.d.ts.map +1 -0
- package/dist/types/lib/mcp/gatewayTransport.d.ts +104 -0
- package/dist/types/lib/mcp/gatewayTransport.d.ts.map +1 -0
- package/dist/types/lib/mcp/index.d.ts +2 -1
- package/dist/types/lib/mcp/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/lib/mcp/types.d.ts +45 -1
- package/dist/types/lib/mcp/types.d.ts.map +1 -1
- package/dist/types/memory/store/types.d.ts +22 -0
- package/dist/types/memory/store/types.d.ts.map +1 -1
- package/dist/types/memory-providers.d.ts +1 -1
- package/dist/types/memory-providers.d.ts.map +1 -1
- package/dist/types/security/index.d.ts +10 -1
- package/dist/types/security/index.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +6 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/package.json +27 -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,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosting/httpHost — one HTTP host, parameterised by the JSON dialect it speaks.
|
|
3
|
+
*
|
|
4
|
+
* Everything hard about serving an agent over HTTP happens once, here: draining
|
|
5
|
+
* on close, aborting when the caller hangs up, failing a handler that throws,
|
|
6
|
+
* failing a handler that answers nothing, mapping refusal codes onto status
|
|
7
|
+
* codes, and choosing between one JSON body and Server-Sent Events based on
|
|
8
|
+
* what the caller asked for.
|
|
9
|
+
*
|
|
10
|
+
* Everything a deployment target gets to re-decide is an {@link HttpWire}: the
|
|
11
|
+
* two paths, and the five JSON shapes — how a request names its input and its
|
|
12
|
+
* session, and what a health probe, a completion, a failure and a streamed
|
|
13
|
+
* piece look like on the wire.
|
|
14
|
+
*
|
|
15
|
+
* ── Why this file exists ─────────────────────────────────────────────────────
|
|
16
|
+
* `nodeHost` shipped first and hard-coded its own dialect: `{ input }` in,
|
|
17
|
+
* `{ output }` out, `{ status: 'ok' }` on the health path. That was fine while
|
|
18
|
+
* it was the only HTTP adapter and wrong the moment there was a second one. A
|
|
19
|
+
* container runtime dictates its own body shape as surely as it dictates its
|
|
20
|
+
* paths, and an adapter for it should be a CONFIGURATION of the HTTP work, not
|
|
21
|
+
* a second copy of it — a copy is where the drain semantics of two adapters
|
|
22
|
+
* silently diverge.
|
|
23
|
+
*
|
|
24
|
+
* Note what did NOT have to change for that: the ports. `AgentHost`,
|
|
25
|
+
* `HostRequest` and `HostReply` say exactly what they said before. The gap was
|
|
26
|
+
* in the first adapter, which had no seam, not in the port, which needed none.
|
|
27
|
+
*
|
|
28
|
+
* Pattern: Template method via configuration (Strategy on the wire format).
|
|
29
|
+
* Everything HTTP lives here and in the wires; `types.ts` knows none of it.
|
|
30
|
+
*/
|
|
31
|
+
import type { AgentHost, HostCapability, HostHandle, HostHandler } from './types.js';
|
|
32
|
+
/** Everything a {@link HttpWire} may read when pulling a request apart. */
|
|
33
|
+
export interface HttpRequestFacts {
|
|
34
|
+
/** The parsed JSON body, or `{}` for an empty one. */
|
|
35
|
+
readonly body: Readonly<Record<string, unknown>>;
|
|
36
|
+
/** Request headers with lower-cased names — so a wire never has to guess casing. */
|
|
37
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
38
|
+
/** The query string, already parsed. */
|
|
39
|
+
readonly query: URLSearchParams;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The JSON dialect one deployment target speaks.
|
|
43
|
+
*
|
|
44
|
+
* A wire is pure: it reads facts and returns values. It never touches the
|
|
45
|
+
* socket, never decides a status code, and never knows whether the reply is
|
|
46
|
+
* going out as one body or as a stream of frames — those are {@link httpHost}'s
|
|
47
|
+
* job, identical for every wire, which is the entire point of separating them.
|
|
48
|
+
*/
|
|
49
|
+
export interface HttpWire {
|
|
50
|
+
/**
|
|
51
|
+
* Pull the port's vocabulary out of one request. Anything the wire cannot
|
|
52
|
+
* find is simply absent — a missing input is the empty string, and a missing
|
|
53
|
+
* session id means "no session", never an error, because refusing a request
|
|
54
|
+
* on the shape of its body is a policy decision that belongs above the
|
|
55
|
+
* transport.
|
|
56
|
+
*/
|
|
57
|
+
readRequest(facts: HttpRequestFacts): {
|
|
58
|
+
readonly input: string;
|
|
59
|
+
readonly sessionId?: string;
|
|
60
|
+
};
|
|
61
|
+
/** Body for a health probe. `uptimeMs` is how long this host has been serving. */
|
|
62
|
+
health(uptimeMs: number): unknown;
|
|
63
|
+
/** Body for a reply that completed. */
|
|
64
|
+
output(output: string): unknown;
|
|
65
|
+
/** Body for a reply that failed. `code` is the refusal's stable code, when it has one. */
|
|
66
|
+
failure(message: string, code?: string): unknown;
|
|
67
|
+
/** Body for one streamed piece, when the caller asked for Server-Sent Events. */
|
|
68
|
+
chunk(text: string): unknown;
|
|
69
|
+
}
|
|
70
|
+
/** Options for {@link httpHost}. */
|
|
71
|
+
export interface HttpHostOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Which adapter this is. Every refusal names it, so a caller reading an error
|
|
74
|
+
* learns which adapter said no rather than which file it came from.
|
|
75
|
+
*/
|
|
76
|
+
readonly name: string;
|
|
77
|
+
/** The JSON dialect this host speaks. */
|
|
78
|
+
readonly wire: HttpWire;
|
|
79
|
+
/**
|
|
80
|
+
* Path that takes a request. **Required, deliberately.** A default here would
|
|
81
|
+
* be inherited by every adapter built on this file, and a default that
|
|
82
|
+
* silently matched one runtime's container contract is exactly how a vendor
|
|
83
|
+
* leaks into a library that promises not to know about one.
|
|
84
|
+
*/
|
|
85
|
+
readonly invokePath: string;
|
|
86
|
+
/** Path that answers a health probe. Required, for the same reason. */
|
|
87
|
+
readonly healthPath: string;
|
|
88
|
+
/** Port to bind. Default `8080`. Pass `0` for an ephemeral port. */
|
|
89
|
+
readonly port?: number;
|
|
90
|
+
/** Interface to bind. Default `'0.0.0.0'`. */
|
|
91
|
+
readonly hostname?: string;
|
|
92
|
+
/** What this adapter claims beyond the baseline. Default `['streaming']`. */
|
|
93
|
+
readonly capabilities?: readonly HostCapability[];
|
|
94
|
+
}
|
|
95
|
+
/** A {@link HostHandle} that also says where it landed. */
|
|
96
|
+
export interface HttpHostHandle extends HostHandle {
|
|
97
|
+
/** Where it is actually listening, e.g. `http://127.0.0.1:53211`. */
|
|
98
|
+
readonly url: string;
|
|
99
|
+
/** The port it actually bound — the real one, when you asked for `0`. */
|
|
100
|
+
readonly port: number;
|
|
101
|
+
}
|
|
102
|
+
/** {@link AgentHost} narrowed to an HTTP handle. */
|
|
103
|
+
export interface HttpHost extends AgentHost {
|
|
104
|
+
serve(handler: HostHandler): Promise<HttpHostHandle>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* An HTTP host for one handler, speaking the dialect you hand it.
|
|
108
|
+
*
|
|
109
|
+
* @example The same machinery, two dialects
|
|
110
|
+
* httpHost({ name: 'nodeHost', wire: jsonWire, invokePath: '/invoke', healthPath: '/health' });
|
|
111
|
+
* httpHost({ name: 'myRuntime', wire: myWire, invokePath: '/v1/run', healthPath: '/up' });
|
|
112
|
+
*/
|
|
113
|
+
export declare function httpHost(options: HttpHostOptions): HttpHost;
|
|
114
|
+
/**
|
|
115
|
+
* Read a header case-insensitively from already-lower-cased facts.
|
|
116
|
+
*
|
|
117
|
+
* Exported because every wire that maps a header needs it and re-deriving it
|
|
118
|
+
* per adapter is how one of them ends up matching only the exact casing the
|
|
119
|
+
* author happened to test with.
|
|
120
|
+
*/
|
|
121
|
+
export declare function headerValue(facts: HttpRequestFacts, name: string, ...fallbacks: string[]): string | undefined;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosting/httpHost — one HTTP host, parameterised by the JSON dialect it speaks.
|
|
3
|
+
*
|
|
4
|
+
* Everything hard about serving an agent over HTTP happens once, here: draining
|
|
5
|
+
* on close, aborting when the caller hangs up, failing a handler that throws,
|
|
6
|
+
* failing a handler that answers nothing, mapping refusal codes onto status
|
|
7
|
+
* codes, and choosing between one JSON body and Server-Sent Events based on
|
|
8
|
+
* what the caller asked for.
|
|
9
|
+
*
|
|
10
|
+
* Everything a deployment target gets to re-decide is an {@link HttpWire}: the
|
|
11
|
+
* two paths, and the five JSON shapes — how a request names its input and its
|
|
12
|
+
* session, and what a health probe, a completion, a failure and a streamed
|
|
13
|
+
* piece look like on the wire.
|
|
14
|
+
*
|
|
15
|
+
* ── Why this file exists ─────────────────────────────────────────────────────
|
|
16
|
+
* `nodeHost` shipped first and hard-coded its own dialect: `{ input }` in,
|
|
17
|
+
* `{ output }` out, `{ status: 'ok' }` on the health path. That was fine while
|
|
18
|
+
* it was the only HTTP adapter and wrong the moment there was a second one. A
|
|
19
|
+
* container runtime dictates its own body shape as surely as it dictates its
|
|
20
|
+
* paths, and an adapter for it should be a CONFIGURATION of the HTTP work, not
|
|
21
|
+
* a second copy of it — a copy is where the drain semantics of two adapters
|
|
22
|
+
* silently diverge.
|
|
23
|
+
*
|
|
24
|
+
* Note what did NOT have to change for that: the ports. `AgentHost`,
|
|
25
|
+
* `HostRequest` and `HostReply` say exactly what they said before. The gap was
|
|
26
|
+
* in the first adapter, which had no seam, not in the port, which needed none.
|
|
27
|
+
*
|
|
28
|
+
* Pattern: Template method via configuration (Strategy on the wire format).
|
|
29
|
+
* Everything HTTP lives here and in the wires; `types.ts` knows none of it.
|
|
30
|
+
*/
|
|
31
|
+
import { encodeSSE } from '../stream.js';
|
|
32
|
+
import { HostClosedError } from './errors.js';
|
|
33
|
+
/**
|
|
34
|
+
* Status codes mapped by refusal code. Anything else is a 500.
|
|
35
|
+
*
|
|
36
|
+
* None of the three is a 5xx: none of them is the agent breaking. A closed host
|
|
37
|
+
* is shutting down (503), a concurrent turn conflicts with the run already
|
|
38
|
+
* going (409), and a paused run conflicts with the state this reply can carry
|
|
39
|
+
* (409) — the run is unfinished, not failed, and a 500 would say otherwise to
|
|
40
|
+
* every dashboard that ever sees it.
|
|
41
|
+
*/
|
|
42
|
+
const STATUS_BY_CODE = {
|
|
43
|
+
ERR_HOST_CLOSED: 503,
|
|
44
|
+
ERR_CONCURRENT_RUN: 409,
|
|
45
|
+
ERR_PAUSE_NOT_CARRIED: 409,
|
|
46
|
+
};
|
|
47
|
+
const DEFAULT_CAPABILITIES = ['streaming'];
|
|
48
|
+
/**
|
|
49
|
+
* An HTTP host for one handler, speaking the dialect you hand it.
|
|
50
|
+
*
|
|
51
|
+
* @example The same machinery, two dialects
|
|
52
|
+
* httpHost({ name: 'nodeHost', wire: jsonWire, invokePath: '/invoke', healthPath: '/health' });
|
|
53
|
+
* httpHost({ name: 'myRuntime', wire: myWire, invokePath: '/v1/run', healthPath: '/up' });
|
|
54
|
+
*/
|
|
55
|
+
export function httpHost(options) {
|
|
56
|
+
const { name, wire, invokePath, healthPath } = options;
|
|
57
|
+
const port = options.port ?? 8080;
|
|
58
|
+
const hostname = options.hostname ?? '0.0.0.0';
|
|
59
|
+
const capabilities = options.capabilities ?? DEFAULT_CAPABILITIES;
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
capabilities,
|
|
63
|
+
async serve(handler) {
|
|
64
|
+
const { createServer } = await import('node:http');
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
const inFlight = new Set();
|
|
67
|
+
let accepting = true;
|
|
68
|
+
let closing;
|
|
69
|
+
const server = createServer((req, res) => {
|
|
70
|
+
const path = (req.url ?? '').split('?')[0];
|
|
71
|
+
if (req.method === 'GET' && path === healthPath) {
|
|
72
|
+
sendJson(res, 200, wire.health(Date.now() - startedAt));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (req.method !== 'POST' || path !== invokePath) {
|
|
76
|
+
sendJson(res, 404, wire.failure(`no route for ${req.method ?? '?'} ${path}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!accepting) {
|
|
80
|
+
const refusal = new HostClosedError(name);
|
|
81
|
+
sendJson(res, STATUS_BY_CODE[refusal.code] ?? 500, wire.failure(refusal.message, refusal.code));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const served = serveOne(req, res, handler, wire);
|
|
85
|
+
inFlight.add(served);
|
|
86
|
+
void served.finally(() => inFlight.delete(served));
|
|
87
|
+
});
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
server.once('error', reject);
|
|
90
|
+
server.listen(port, hostname, resolve);
|
|
91
|
+
});
|
|
92
|
+
const address = server.address();
|
|
93
|
+
const boundPort = typeof address === 'object' && address ? address.port : port;
|
|
94
|
+
const displayHost = hostname === '0.0.0.0' || hostname === '::' ? '127.0.0.1' : hostname;
|
|
95
|
+
return {
|
|
96
|
+
url: `http://${displayHost}:${boundPort}`,
|
|
97
|
+
port: boundPort,
|
|
98
|
+
close() {
|
|
99
|
+
// Idempotent: the first call owns the shutdown, later ones await it.
|
|
100
|
+
closing ??= (async () => {
|
|
101
|
+
accepting = false;
|
|
102
|
+
// Drain BEFORE touching sockets — an in-flight request is work the
|
|
103
|
+
// caller is still waiting on, and dropping it would be the exact
|
|
104
|
+
// thing close() promises not to do.
|
|
105
|
+
await Promise.allSettled([...inFlight]);
|
|
106
|
+
server.closeIdleConnections();
|
|
107
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
108
|
+
})();
|
|
109
|
+
return closing;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Run one request through the handler and write whatever it decides. */
|
|
116
|
+
async function serveOne(req, res, handler, wire) {
|
|
117
|
+
const wantsStream = (req.headers.accept ?? '').includes('text/event-stream');
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
let body;
|
|
120
|
+
try {
|
|
121
|
+
body = await readJson(req);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
sendJson(res, 400, wire.failure(`invalid JSON body: ${asError(err).message}`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const headers = lowerCased(req.headers);
|
|
128
|
+
const query = new URLSearchParams((req.url ?? '').split('?')[1] ?? '');
|
|
129
|
+
const { input, sessionId } = wire.readRequest({ body, headers, query });
|
|
130
|
+
if (wantsStream) {
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'content-type': 'text/event-stream',
|
|
133
|
+
'cache-control': 'no-cache',
|
|
134
|
+
connection: 'keep-alive',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
let settled = false;
|
|
138
|
+
// The caller hung up before we answered — tell the handler so it can stop
|
|
139
|
+
// paying for work nobody is waiting for.
|
|
140
|
+
res.once('close', () => {
|
|
141
|
+
if (!settled)
|
|
142
|
+
controller.abort();
|
|
143
|
+
});
|
|
144
|
+
const reply = {
|
|
145
|
+
emit(chunk) {
|
|
146
|
+
// Not streaming? The chunk is a preview of text `complete` will deliver
|
|
147
|
+
// in full, so there is nothing to send and nothing to keep.
|
|
148
|
+
if (settled || !wantsStream)
|
|
149
|
+
return;
|
|
150
|
+
res.write(encodeSSE('chunk', wire.chunk(chunk)));
|
|
151
|
+
},
|
|
152
|
+
complete(output) {
|
|
153
|
+
if (settled)
|
|
154
|
+
return;
|
|
155
|
+
settled = true;
|
|
156
|
+
if (wantsStream) {
|
|
157
|
+
res.write(encodeSSE('complete', wire.output(output)));
|
|
158
|
+
res.end();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
sendJson(res, 200, wire.output(output));
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
fail(error) {
|
|
165
|
+
if (settled)
|
|
166
|
+
return;
|
|
167
|
+
settled = true;
|
|
168
|
+
const code = error.code;
|
|
169
|
+
const payload = wire.failure(error.message, code);
|
|
170
|
+
if (wantsStream) {
|
|
171
|
+
res.write(encodeSSE('error', payload));
|
|
172
|
+
res.end();
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
sendJson(res, code !== undefined ? STATUS_BY_CODE[code] ?? 500 : 500, payload);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
await handler({
|
|
181
|
+
input,
|
|
182
|
+
...(sessionId !== undefined && { sessionId }),
|
|
183
|
+
headers,
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
}, reply);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
// A handler that throws is a failed request, never a hung one.
|
|
189
|
+
reply.fail(asError(err));
|
|
190
|
+
}
|
|
191
|
+
// A handler that returned without answering gets one authored answer rather
|
|
192
|
+
// than a socket the caller waits on until it times out.
|
|
193
|
+
if (!settled) {
|
|
194
|
+
reply.fail(new Error('[hosting] the handler returned without calling complete() or fail().'));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function readJson(req) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
req.on('data', (c) => chunks.push(c));
|
|
201
|
+
req.on('error', reject);
|
|
202
|
+
req.on('end', () => {
|
|
203
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
204
|
+
if (!raw)
|
|
205
|
+
return resolve({});
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(raw);
|
|
208
|
+
resolve(parsed && typeof parsed === 'object' ? parsed : {});
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
reject(err);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function sendJson(res, status, body) {
|
|
217
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
218
|
+
res.end(JSON.stringify(body));
|
|
219
|
+
}
|
|
220
|
+
function lowerCased(headers) {
|
|
221
|
+
const out = {};
|
|
222
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
223
|
+
if (typeof value === 'string')
|
|
224
|
+
out[name.toLowerCase()] = value;
|
|
225
|
+
else if (Array.isArray(value))
|
|
226
|
+
out[name.toLowerCase()] = value.join(', ');
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
function asError(err) {
|
|
231
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Read a header case-insensitively from already-lower-cased facts.
|
|
235
|
+
*
|
|
236
|
+
* Exported because every wire that maps a header needs it and re-deriving it
|
|
237
|
+
* per adapter is how one of them ends up matching only the exact casing the
|
|
238
|
+
* author happened to test with.
|
|
239
|
+
*/
|
|
240
|
+
export function headerValue(facts, name, ...fallbacks) {
|
|
241
|
+
for (const candidate of [name, ...fallbacks]) {
|
|
242
|
+
const found = facts.headers[candidate.toLowerCase()];
|
|
243
|
+
if (typeof found === 'string' && found.length > 0)
|
|
244
|
+
return found;
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=httpHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpHost.js","sourceRoot":"","sources":["../../../src/hosting/httpHost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
* • `httpHost({ name, wire, invokePath, healthPath, ... })` — the HTTP work
|
|
31
|
+
* itself, parameterised by the JSON dialect it speaks. `nodeHost` is one
|
|
32
|
+
* configuration of it; an adapter for someone's container runtime is
|
|
33
|
+
* another. Two paths and five body shapes are all a second HTTP adapter
|
|
34
|
+
* re-decides.
|
|
35
|
+
* • `memorySessions()` — conversations in a Map, for tests and local dev.
|
|
36
|
+
* • `standingAgent({ agent, sessions, host })` — the composer.
|
|
37
|
+
* • `toEnvelope` / `readEnvelope` — pack a conversation, and refuse by name to
|
|
38
|
+
* unpack a format this runtime does not know.
|
|
39
|
+
* • `requireCapability` — feature-detection with teeth.
|
|
40
|
+
*
|
|
41
|
+
* @example An agent that stays up and remembers
|
|
42
|
+
* import { Agent } from 'agentfootprint';
|
|
43
|
+
* import { standingAgent, nodeHost, memorySessions } from 'agentfootprint/hosting';
|
|
44
|
+
*
|
|
45
|
+
* const handle = await standingAgent({
|
|
46
|
+
* agent: Agent.create({ provider, model }).system('You help customers.').build(),
|
|
47
|
+
* sessions: memorySessions(),
|
|
48
|
+
* host: nodeHost({ port: 8080 }),
|
|
49
|
+
* });
|
|
50
|
+
* process.on('SIGTERM', () => void handle.close());
|
|
51
|
+
*/
|
|
52
|
+
export { nodeHost, jsonWire } from './nodeHost.js';
|
|
53
|
+
export type { NodeHost, NodeHostHandle, NodeHostOptions } from './nodeHost.js';
|
|
54
|
+
export { httpHost, headerValue } from './httpHost.js';
|
|
55
|
+
export type { HttpHost, HttpHostHandle, HttpHostOptions, HttpRequestFacts, HttpWire, } from './httpHost.js';
|
|
56
|
+
export { memorySessions } from './memorySessions.js';
|
|
57
|
+
export { toEnvelope, readEnvelope } from './envelope.js';
|
|
58
|
+
export { standingAgent } from './standingAgent.js';
|
|
59
|
+
export { requireCapability, HostClosedError, ConcurrentRunError, PauseNotCarriedError, } from './errors.js';
|
|
60
|
+
export type { AgentHost, CheckpointEnvelope, ConcurrentInvokePolicy, HostCapability, HostHandle, HostHandler, HostReply, HostRequest, SessionLifecycle, StandingAgentOptions, WakeReason, } from './types.js';
|