agentfootprint 7.12.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 (143) hide show
  1. package/dist/core/Agent.js +118 -7
  2. package/dist/core/Agent.js.map +1 -1
  3. package/dist/core/agent/AgentBuilder.js +59 -1
  4. package/dist/core/agent/AgentBuilder.js.map +1 -1
  5. package/dist/core/agent/buildAgentChart.js +6 -0
  6. package/dist/core/agent/buildAgentChart.js.map +1 -1
  7. package/dist/core/agent/buildDynamicAgentChart.js +14 -0
  8. package/dist/core/agent/buildDynamicAgentChart.js.map +1 -1
  9. package/dist/core/agent/stages/callLLM.js +10 -4
  10. package/dist/core/agent/stages/callLLM.js.map +1 -1
  11. package/dist/core/agent/stages/seed.js +16 -0
  12. package/dist/core/agent/stages/seed.js.map +1 -1
  13. package/dist/core/slots/buildSystemPromptSlot.js +2 -1
  14. package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
  15. package/dist/esm/core/Agent.d.ts +54 -3
  16. package/dist/esm/core/Agent.js +118 -7
  17. package/dist/esm/core/Agent.js.map +1 -1
  18. package/dist/esm/core/agent/AgentBuilder.d.ts +48 -1
  19. package/dist/esm/core/agent/AgentBuilder.js +59 -1
  20. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  21. package/dist/esm/core/agent/buildAgentChart.js +6 -0
  22. package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
  23. package/dist/esm/core/agent/buildDynamicAgentChart.js +14 -0
  24. package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -1
  25. package/dist/esm/core/agent/stages/callLLM.d.ts +9 -0
  26. package/dist/esm/core/agent/stages/callLLM.js +10 -4
  27. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  28. package/dist/esm/core/agent/stages/seed.d.ts +10 -1
  29. package/dist/esm/core/agent/stages/seed.js +16 -0
  30. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  31. package/dist/esm/core/agent/types.d.ts +44 -0
  32. package/dist/esm/core/slots/buildSystemPromptSlot.d.ts +19 -4
  33. package/dist/esm/core/slots/buildSystemPromptSlot.js +2 -1
  34. package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
  35. package/dist/esm/hosting/envelope.d.ts +33 -0
  36. package/dist/esm/hosting/envelope.js +49 -0
  37. package/dist/esm/hosting/envelope.js.map +1 -0
  38. package/dist/esm/hosting/errors.d.ts +78 -0
  39. package/dist/esm/hosting/errors.js +119 -0
  40. package/dist/esm/hosting/errors.js.map +1 -0
  41. package/dist/esm/hosting/index.d.ts +53 -0
  42. package/dist/esm/hosting/index.js +52 -0
  43. package/dist/esm/hosting/index.js.map +1 -0
  44. package/dist/esm/hosting/memorySessions.d.ts +22 -0
  45. package/dist/esm/hosting/memorySessions.js +31 -0
  46. package/dist/esm/hosting/memorySessions.js.map +1 -0
  47. package/dist/esm/hosting/nodeHost.d.ts +64 -0
  48. package/dist/esm/hosting/nodeHost.js +245 -0
  49. package/dist/esm/hosting/nodeHost.js.map +1 -0
  50. package/dist/esm/hosting/standingAgent.d.ts +62 -0
  51. package/dist/esm/hosting/standingAgent.js +192 -0
  52. package/dist/esm/hosting/standingAgent.js.map +1 -0
  53. package/dist/esm/hosting/types.d.ts +206 -0
  54. package/dist/esm/hosting/types.js +21 -0
  55. package/dist/esm/hosting/types.js.map +1 -0
  56. package/dist/esm/lib/injection-engine/index.d.ts +1 -0
  57. package/dist/esm/lib/injection-engine/index.js +4 -0
  58. package/dist/esm/lib/injection-engine/index.js.map +1 -1
  59. package/dist/esm/lib/injection-engine/skillsFromDir.d.ts +80 -0
  60. package/dist/esm/lib/injection-engine/skillsFromDir.js +243 -0
  61. package/dist/esm/lib/injection-engine/skillsFromDir.js.map +1 -0
  62. package/dist/esm/lib/mcp/index.d.ts +6 -5
  63. package/dist/esm/lib/mcp/index.js +5 -4
  64. package/dist/esm/lib/mcp/index.js.map +1 -1
  65. package/dist/esm/lib/mcp/mcpClient.js +21 -9
  66. package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
  67. package/dist/esm/lib/mcp/mcpServe.d.ts +70 -0
  68. package/dist/esm/lib/mcp/mcpServe.js +374 -0
  69. package/dist/esm/lib/mcp/mcpServe.js.map +1 -0
  70. package/dist/esm/lib/mcp/types.d.ts +130 -17
  71. package/dist/esm/lib/mcp/types.js +10 -10
  72. package/dist/esm/tool-providers/index.d.ts +8 -2
  73. package/dist/esm/tool-providers/index.js +7 -1
  74. package/dist/esm/tool-providers/index.js.map +1 -1
  75. package/dist/hosting/envelope.js +54 -0
  76. package/dist/hosting/envelope.js.map +1 -0
  77. package/dist/hosting/errors.js +126 -0
  78. package/dist/hosting/errors.js.map +1 -0
  79. package/dist/hosting/index.js +64 -0
  80. package/dist/hosting/index.js.map +1 -0
  81. package/dist/hosting/memorySessions.js +35 -0
  82. package/dist/hosting/memorySessions.js.map +1 -0
  83. package/dist/hosting/nodeHost.js +272 -0
  84. package/dist/hosting/nodeHost.js.map +1 -0
  85. package/dist/hosting/standingAgent.js +196 -0
  86. package/dist/hosting/standingAgent.js.map +1 -0
  87. package/dist/hosting/types.js +22 -0
  88. package/dist/hosting/types.js.map +1 -0
  89. package/dist/lib/injection-engine/index.js +6 -1
  90. package/dist/lib/injection-engine/index.js.map +1 -1
  91. package/dist/lib/injection-engine/skillsFromDir.js +270 -0
  92. package/dist/lib/injection-engine/skillsFromDir.js.map +1 -0
  93. package/dist/lib/mcp/index.js +7 -5
  94. package/dist/lib/mcp/index.js.map +1 -1
  95. package/dist/lib/mcp/mcpClient.js +21 -9
  96. package/dist/lib/mcp/mcpClient.js.map +1 -1
  97. package/dist/lib/mcp/mcpServe.js +401 -0
  98. package/dist/lib/mcp/mcpServe.js.map +1 -0
  99. package/dist/lib/mcp/types.js +10 -10
  100. package/dist/tool-providers/index.js +8 -1
  101. package/dist/tool-providers/index.js.map +1 -1
  102. package/dist/types/core/Agent.d.ts +54 -3
  103. package/dist/types/core/Agent.d.ts.map +1 -1
  104. package/dist/types/core/agent/AgentBuilder.d.ts +48 -1
  105. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  106. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  107. package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -1
  108. package/dist/types/core/agent/stages/callLLM.d.ts +9 -0
  109. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  110. package/dist/types/core/agent/stages/seed.d.ts +10 -1
  111. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  112. package/dist/types/core/agent/types.d.ts +44 -0
  113. package/dist/types/core/agent/types.d.ts.map +1 -1
  114. package/dist/types/core/slots/buildSystemPromptSlot.d.ts +19 -4
  115. package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
  116. package/dist/types/hosting/envelope.d.ts +34 -0
  117. package/dist/types/hosting/envelope.d.ts.map +1 -0
  118. package/dist/types/hosting/errors.d.ts +79 -0
  119. package/dist/types/hosting/errors.d.ts.map +1 -0
  120. package/dist/types/hosting/index.d.ts +54 -0
  121. package/dist/types/hosting/index.d.ts.map +1 -0
  122. package/dist/types/hosting/memorySessions.d.ts +23 -0
  123. package/dist/types/hosting/memorySessions.d.ts.map +1 -0
  124. package/dist/types/hosting/nodeHost.d.ts +65 -0
  125. package/dist/types/hosting/nodeHost.d.ts.map +1 -0
  126. package/dist/types/hosting/standingAgent.d.ts +63 -0
  127. package/dist/types/hosting/standingAgent.d.ts.map +1 -0
  128. package/dist/types/hosting/types.d.ts +207 -0
  129. package/dist/types/hosting/types.d.ts.map +1 -0
  130. package/dist/types/lib/injection-engine/index.d.ts +1 -0
  131. package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
  132. package/dist/types/lib/injection-engine/skillsFromDir.d.ts +81 -0
  133. package/dist/types/lib/injection-engine/skillsFromDir.d.ts.map +1 -0
  134. package/dist/types/lib/mcp/index.d.ts +6 -5
  135. package/dist/types/lib/mcp/index.d.ts.map +1 -1
  136. package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
  137. package/dist/types/lib/mcp/mcpServe.d.ts +71 -0
  138. package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -0
  139. package/dist/types/lib/mcp/types.d.ts +130 -17
  140. package/dist/types/lib/mcp/types.d.ts.map +1 -1
  141. package/dist/types/tool-providers/index.d.ts +8 -2
  142. package/dist/types/tool-providers/index.d.ts.map +1 -1
  143. package/package.json +15 -1
@@ -1,11 +1,14 @@
1
1
  /**
2
- * MCP — Model Context Protocol client integration.
2
+ * MCP — Model Context Protocol, in both directions.
3
3
  *
4
4
  * MCP (https://modelcontextprotocol.io) is an open standard for
5
- * connecting LLMs to external tools and data sources. agentfootprint's
6
- * MCP adapter is **client-only** — it consumes MCP servers and exposes
7
- * their tools as agentfootprint `Tool[]` so consumers can plug them
8
- * straight into `agent.tool(...)`.
5
+ * connecting LLMs to external tools and data sources. This module
6
+ * bridges it both ways:
7
+ *
8
+ * - `mcpClient(opts)` — consume an MCP server; its tools become
9
+ * agentfootprint `Tool[]` you can hand to `agent.tools(...)`.
10
+ * - `mcpServe(tools, opts)` — the other direction: expose your own
11
+ * `Tool[]` AS an MCP server, so any MCP client can call them.
9
12
  *
10
13
  * Pattern: Adapter (GoF) — translates MCP wire format ↔ agentfootprint
11
14
  * `Tool` interface. The MCP SDK does the protocol work; we
@@ -14,11 +17,8 @@
14
17
  * inline alternative for non-MCP tools).
15
18
  * Emits: N/A directly — wrapped tools emit the standard
16
19
  * `agentfootprint.stream.tool_start` / `tool_end` events
17
- * when the agent calls them.
18
- *
19
- * Server-side support (exposing an agent or LLMCall as an MCP tool)
20
- * is a separate concern not yet shipped. This module covers the
21
- * 80% case: pulling an existing MCP server's tools INTO an agent.
20
+ * when the agent calls them. `mcpServe` runs OUTSIDE any
21
+ * agent run, so it emits nothing at all.
22
22
  */
23
23
  import type { Tool } from '../../core/tools.js';
24
24
  /**
@@ -32,7 +32,13 @@ export interface McpStdioTransport {
32
32
  readonly command: string;
33
33
  /** CLI args passed to the executable. */
34
34
  readonly args?: readonly string[];
35
- /** Optional env vars set on the subprocess. */
35
+ /**
36
+ * Environment for the subprocess. Passed to the SDK as-is, and the SDK
37
+ * treats it as the WHOLE environment: set this and the child no longer
38
+ * inherits the safe default set (`PATH`, `HOME`, …). Omit it unless you
39
+ * mean to replace the environment; to merely add a variable, include the
40
+ * ones the child still needs.
41
+ */
36
42
  readonly env?: Readonly<Record<string, string>>;
37
43
  /** Working directory for the subprocess. */
38
44
  readonly cwd?: string;
@@ -107,22 +113,29 @@ export interface McpClient {
107
113
  * a lazy peer-dep)
108
114
  *
109
115
  * The real SDK exports a richer surface; we narrow to what's needed.
116
+ *
117
+ * Argument POSITION matters here and is easy to get wrong: the SDK keeps
118
+ * per-request options (`signal`, `timeout`) in a SEPARATE trailing
119
+ * argument, never inside the JSON-RPC params. A `signal` smuggled into
120
+ * the params object is serialized onto the wire as `{}` and silently
121
+ * fails to cancel anything, so these signatures mirror the SDK's own
122
+ * shape rather than a flattened convenience version of it.
110
123
  */
111
124
  export interface McpSdkClient {
112
- connect(transport: unknown): Promise<void>;
113
- listTools(): Promise<{
125
+ connect(transport: unknown, options?: McpRequestOptions): Promise<void>;
126
+ listTools(params?: undefined, options?: McpRequestOptions): Promise<{
114
127
  readonly tools: ReadonlyArray<{
115
128
  readonly name: string;
116
129
  readonly description?: string;
117
130
  readonly inputSchema: Readonly<Record<string, unknown>>;
118
131
  }>;
119
132
  }>;
120
- callTool(args: {
133
+ callTool(params: {
121
134
  readonly name: string;
122
135
  readonly arguments?: Readonly<Record<string, unknown>>;
123
- /** Forwarded from `McpClientOptions.signal` so consumers can cancel hung tool calls. */
124
- readonly signal?: AbortSignal;
125
- }): Promise<{
136
+ },
137
+ /** The SDK's result schema. We always want its default, so we pass `undefined`. */
138
+ resultSchema?: undefined, options?: McpRequestOptions): Promise<{
126
139
  readonly content: ReadonlyArray<{
127
140
  readonly type: string;
128
141
  readonly text?: string;
@@ -131,3 +144,103 @@ export interface McpSdkClient {
131
144
  }>;
132
145
  close(): Promise<void>;
133
146
  }
147
+ /**
148
+ * The SDK's trailing per-request options argument, narrowed to the one
149
+ * field we forward from {@link McpClientOptions.signal}.
150
+ */
151
+ export interface McpRequestOptions {
152
+ readonly signal?: AbortSignal;
153
+ }
154
+ /**
155
+ * `stdio` transport — the server speaks MCP over its own stdin/stdout.
156
+ * This is how a desktop MCP host launches a server: it spawns your
157
+ * process and talks down the pipe. The default.
158
+ *
159
+ * One consequence worth stating out loud: stdout belongs to the
160
+ * protocol. Anything else your process prints there corrupts the
161
+ * stream, so log to stderr.
162
+ */
163
+ export interface McpStdioServeTransport {
164
+ readonly transport: 'stdio';
165
+ }
166
+ /**
167
+ * `http` transport — the server listens on a port and speaks MCP over
168
+ * Streamable HTTP. Stateless (no session ids): every request is
169
+ * self-contained, which is what makes it safe to run several replicas
170
+ * behind a load balancer.
171
+ */
172
+ export interface McpHttpServeTransport {
173
+ readonly transport: 'http';
174
+ /** TCP port to listen on. */
175
+ readonly port: number;
176
+ /** Interface to bind. Defaults to Node's own default (all interfaces). */
177
+ readonly host?: string;
178
+ /** URL path the MCP endpoint answers on. Default `'/mcp'`. */
179
+ readonly path?: string;
180
+ }
181
+ export type McpServeTransport = McpStdioServeTransport | McpHttpServeTransport;
182
+ export interface McpServeOptions {
183
+ /**
184
+ * Server name reported to clients on connect. Surfaces in the
185
+ * client's server list. Default `'agentfootprint'`.
186
+ */
187
+ readonly name?: string;
188
+ /** Server version reported to clients. Default `'0.0.0'`. */
189
+ readonly version?: string;
190
+ /** Transport configuration. Default `{ transport: 'stdio' }`. */
191
+ readonly transport?: McpServeTransport;
192
+ /**
193
+ * Credential provider for served tools that declare `needs`. The
194
+ * credential is resolved BEFORE `execute` and injected as
195
+ * `ctx.credential`, exactly as the Agent's tool-call stage does it.
196
+ *
197
+ * Serving a `needs`-declaring tool without this is refused at
198
+ * construction: the tool would run with `ctx.credential` undefined
199
+ * and fail somewhere further in, or worse, not fail at all.
200
+ */
201
+ readonly credentials?: import('../../identity/types.js').CredentialProvider;
202
+ /**
203
+ * @internal Pre-built SDK server for tests. Skips SDK import +
204
+ * transport construction. Mirrors `McpClientOptions._client`.
205
+ */
206
+ readonly _server?: McpSdkServer;
207
+ }
208
+ /**
209
+ * What `mcpServe(...)` returns. The server is already listening by the
210
+ * time you hold this; `close()` is the only thing left to do.
211
+ */
212
+ export interface McpServeHandle {
213
+ /** Server name from options (or the default). */
214
+ readonly name: string;
215
+ /** Names of the tools being served, in the order clients will list them. */
216
+ readonly toolNames: readonly string[];
217
+ /**
218
+ * Stop serving: closes the transport (and, for `http`, the listening
219
+ * socket) and then the MCP server. Idempotent — calling it twice is
220
+ * not an error, so a shutdown hook and an explicit close can coexist.
221
+ */
222
+ close(): Promise<void>;
223
+ }
224
+ /**
225
+ * Minimal structural type for the parts of the MCP SDK's low-level
226
+ * `Server` we touch. Defined locally for the same two reasons as
227
+ * {@link McpSdkClient}: test injection via `McpServeOptions._server`,
228
+ * and no hard import on the lazy peer-dep.
229
+ */
230
+ export interface McpSdkServer {
231
+ setRequestHandler(schema: unknown, handler: (request: McpCallToolRequest, extra: {
232
+ readonly signal?: AbortSignal;
233
+ }) => unknown): void;
234
+ connect(transport: unknown): Promise<void>;
235
+ close(): Promise<void>;
236
+ }
237
+ /**
238
+ * The shape `tools/call` requests arrive in. `tools/list` requests carry
239
+ * no params we read, so one request type covers both handlers.
240
+ */
241
+ export interface McpCallToolRequest {
242
+ readonly params?: {
243
+ readonly name?: string;
244
+ readonly arguments?: unknown;
245
+ };
246
+ }
@@ -1,11 +1,14 @@
1
1
  /**
2
- * MCP — Model Context Protocol client integration.
2
+ * MCP — Model Context Protocol, in both directions.
3
3
  *
4
4
  * MCP (https://modelcontextprotocol.io) is an open standard for
5
- * connecting LLMs to external tools and data sources. agentfootprint's
6
- * MCP adapter is **client-only** — it consumes MCP servers and exposes
7
- * their tools as agentfootprint `Tool[]` so consumers can plug them
8
- * straight into `agent.tool(...)`.
5
+ * connecting LLMs to external tools and data sources. This module
6
+ * bridges it both ways:
7
+ *
8
+ * - `mcpClient(opts)` — consume an MCP server; its tools become
9
+ * agentfootprint `Tool[]` you can hand to `agent.tools(...)`.
10
+ * - `mcpServe(tools, opts)` — the other direction: expose your own
11
+ * `Tool[]` AS an MCP server, so any MCP client can call them.
9
12
  *
10
13
  * Pattern: Adapter (GoF) — translates MCP wire format ↔ agentfootprint
11
14
  * `Tool` interface. The MCP SDK does the protocol work; we
@@ -14,11 +17,8 @@
14
17
  * inline alternative for non-MCP tools).
15
18
  * Emits: N/A directly — wrapped tools emit the standard
16
19
  * `agentfootprint.stream.tool_start` / `tool_end` events
17
- * when the agent calls them.
18
- *
19
- * Server-side support (exposing an agent or LLMCall as an MCP tool)
20
- * is a separate concern not yet shipped. This module covers the
21
- * 80% case: pulling an existing MCP server's tools INTO an agent.
20
+ * when the agent calls them. `mcpServe` runs OUTSIDE any
21
+ * agent run, so it emits nothing at all.
22
22
  */
23
23
  export {};
24
24
  //# sourceMappingURL=types.js.map
@@ -13,6 +13,12 @@
13
13
  * - `mcpClient(opts)` — connect to an MCP server (real)
14
14
  * - `mockMcpClient({ tools })` — in-memory MCP source for dev / tests
15
15
  *
16
+ * 3. Tool sink — the other direction
17
+ * - `mcpServe(tools, opts)` — expose your own `Tool[]` AS an MCP
18
+ * server, so any MCP client can call them. The served tool is the
19
+ * SAME object you pass in, so whatever governance you composed
20
+ * around it still runs.
21
+ *
16
22
  * Compose freely. The dispatch layer is decorator-shaped (mirroring
17
23
  * `withRetry` / `withFallback` over LLMProvider). Tool sources produce
18
24
  * `Tool[]` that flow into a `staticTools(arr)` provider, which can
@@ -37,5 +43,5 @@ export { staticTools } from './staticTools.js';
37
43
  export { gatedTools } from './gatedTools.js';
38
44
  export { skillScopedTools } from './skillScopedTools.js';
39
45
  export type { ToolProvider, ToolDispatchContext, ToolGatePredicate } from './types.js';
40
- export { mcpClient, mockMcpClient } from '../lib/mcp/index.js';
41
- export type { McpClient, McpClientOptions, McpSdkClient, McpTransport, McpStdioTransport, McpHttpTransport, MockMcpClientOptions, MockMcpTool, } from '../lib/mcp/index.js';
46
+ export { mcpClient, mcpServe, mockMcpClient } from '../lib/mcp/index.js';
47
+ export type { McpClient, McpClientOptions, McpSdkClient, McpTransport, McpStdioTransport, McpHttpTransport, MockMcpClientOptions, MockMcpTool, McpServeOptions, McpServeHandle, McpServeTransport, McpStdioServeTransport, McpHttpServeTransport, McpSdkServer, } from '../lib/mcp/index.js';
@@ -13,6 +13,12 @@
13
13
  * - `mcpClient(opts)` — connect to an MCP server (real)
14
14
  * - `mockMcpClient({ tools })` — in-memory MCP source for dev / tests
15
15
  *
16
+ * 3. Tool sink — the other direction
17
+ * - `mcpServe(tools, opts)` — expose your own `Tool[]` AS an MCP
18
+ * server, so any MCP client can call them. The served tool is the
19
+ * SAME object you pass in, so whatever governance you composed
20
+ * around it still runs.
21
+ *
16
22
  * Compose freely. The dispatch layer is decorator-shaped (mirroring
17
23
  * `withRetry` / `withFallback` over LLMProvider). Tool sources produce
18
24
  * `Tool[]` that flow into a `staticTools(arr)` provider, which can
@@ -39,5 +45,5 @@ export { skillScopedTools } from './skillScopedTools.js';
39
45
  // Re-export tool sources from the MCP module so consumers find them in
40
46
  // one place. This subpath is the ONLY place `mcpClient` is public — the
41
47
  // top-level barrel does not re-export it.
42
- export { mcpClient, mockMcpClient } from '../lib/mcp/index.js';
48
+ export { mcpClient, mcpServe, mockMcpClient } from '../lib/mcp/index.js';
43
49
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tool-providers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,uEAAuE;AACvE,wEAAwE;AACxE,0CAA0C;AAC1C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tool-providers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,uEAAuE;AACvE,wEAAwE;AACxE,0CAA0C;AAC1C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /**
3
+ * hosting/envelope — pack a conversation for storage, and refuse to unpack one
4
+ * you cannot read.
5
+ *
6
+ * Two functions and one rule: **an unknown format is refused by name, never
7
+ * guessed at.** A store outlives the code that wrote to it. Somebody will
8
+ * deploy a newer runtime, it will write a newer format, and an older instance
9
+ * still running will read it. The only honest thing that older instance can do
10
+ * is say which format it found, which ones it knows, and stop — because
11
+ * "restore what I can and hope" means an agent answering from a conversation
12
+ * that is missing whatever the older reader did not understand.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.readEnvelope = exports.toEnvelope = void 0;
16
+ const runCheckpoint_js_1 = require("../core/runCheckpoint.js");
17
+ /** Every format this runtime can read. Add, never redefine. */
18
+ const KNOWN_FORMATS = ['conversation-v1'];
19
+ /**
20
+ * Pack a conversation checkpoint for storage.
21
+ *
22
+ * @example
23
+ * const conversation = agent.checkpoint();
24
+ * if (conversation) await sessions.persist(sessionId, toEnvelope(conversation));
25
+ */
26
+ function toEnvelope(checkpoint) {
27
+ return { format: 'conversation-v1', data: checkpoint, savedAt: Date.now() };
28
+ }
29
+ exports.toEnvelope = toEnvelope;
30
+ /**
31
+ * Unpack a stored envelope back into a conversation checkpoint.
32
+ *
33
+ * Takes `unknown` on purpose: what comes back from a store is bytes somebody
34
+ * else wrote, in a format this runtime may not know, and typing the parameter
35
+ * as the happy shape would be assuming the very thing that needs checking.
36
+ *
37
+ * @throws TypeError naming the format when it is one this runtime cannot read,
38
+ * and naming the missing field when the conversation inside is malformed.
39
+ */
40
+ function readEnvelope(envelope) {
41
+ if (!envelope || typeof envelope !== 'object') {
42
+ throw new TypeError(`[hosting] stored session is not an envelope (got ${envelope === null ? 'null' : typeof envelope}). ` + `Expected { format, data, savedAt } as written by toEnvelope().`);
43
+ }
44
+ const found = envelope.format;
45
+ if (typeof found !== 'string' || !KNOWN_FORMATS.includes(found)) {
46
+ throw new TypeError(`[hosting] unknown checkpoint format '${String(found)}'. ` +
47
+ `This runtime reads: ${KNOWN_FORMATS.join(', ')}. ` +
48
+ `Refusing rather than restoring a conversation it cannot read — a newer envelope ` +
49
+ `needs a runtime that knows the format that wrote it.`);
50
+ }
51
+ return (0, runCheckpoint_js_1.validateCheckpoint)(envelope.data);
52
+ }
53
+ exports.readEnvelope = readEnvelope;
54
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/hosting/envelope.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAEH,+DAAuF;AAGvF,+DAA+D;AAC/D,MAAM,aAAa,GAAsB,CAAC,iBAAiB,CAAC,CAAC;AAE7D;;;;;;GAMG;AACH,SAAgB,UAAU,CAAC,UAA8B;IACvD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAC9E,CAAC;AAFD,gCAEC;AAED;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAAC,QAAiB;IAC5C,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CACjB,oDACE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,QACtC,KAAK,GAAG,gEAAgE,CACzE,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAI,QAAwC,CAAC,MAAM,CAAC;IAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CACjB,wCAAwC,MAAM,CAAC,KAAK,CAAC,KAAK;YACxD,uBAAuB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACnD,kFAAkF;YAClF,sDAAsD,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,IAAA,qCAAkB,EAAE,QAA+B,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC;AAlBD,oCAkBC"}
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * hosting/errors — the refusals, authored once so every adapter refuses in the
4
+ * same words.
5
+ *
6
+ * A refusal that varies by adapter is a refusal nobody can write a test or a
7
+ * runbook against. These three carry a stable `code`, name WHO refused, and say
8
+ * what the caller should do instead. Adapters map the codes onto whatever their
9
+ * transport uses to say "no" — that mapping is the adapter's business and lives
10
+ * in the adapter, never here.
11
+ *
12
+ * `requireCapability` is the fourth refusal and the only one that is a
13
+ * programming mistake rather than a runtime condition, so it throws a plain
14
+ * `Error`: nothing branches on "I forgot to feature-detect", it just needs to
15
+ * say so loudly and name the adapter it is talking about.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.requireCapability = exports.PauseNotCarriedError = exports.ConcurrentRunError = exports.HostClosedError = void 0;
19
+ /**
20
+ * Thrown when a request arrives at a host that is shutting down or shut down.
21
+ *
22
+ * `close()` lets in-flight work finish and refuses everything after it; this is
23
+ * what "everything after it" receives.
24
+ */
25
+ class HostClosedError extends Error {
26
+ code = 'ERR_HOST_CLOSED';
27
+ /** Which adapter refused. */
28
+ hostName;
29
+ constructor(hostName) {
30
+ super(`[hosting] the '${hostName}' host is closed and is not accepting new requests. ` +
31
+ `In-flight requests were allowed to finish; this one arrived after close() was called. ` +
32
+ `Serve again on a fresh host to accept new work.`);
33
+ this.name = 'HostClosedError';
34
+ this.hostName = hostName;
35
+ }
36
+ }
37
+ exports.HostClosedError = HostClosedError;
38
+ /**
39
+ * Thrown when a request arrives for a session that already has a run in flight
40
+ * and the policy is `'reject'`.
41
+ *
42
+ * The refusal is about the SESSION, not about load: two turns of one
43
+ * conversation racing each other would each answer from the state the other is
44
+ * about to replace. A request for any OTHER session is never refused — it
45
+ * simply waits.
46
+ */
47
+ class ConcurrentRunError extends Error {
48
+ code = 'ERR_CONCURRENT_RUN';
49
+ /** The session that already has a run going. */
50
+ sessionId;
51
+ /** The run that is already going, when it has announced itself. */
52
+ activeRunId;
53
+ constructor(sessionId, activeRunId) {
54
+ super(`[hosting] session '${sessionId}' already has a run in flight` +
55
+ (activeRunId ? ` (run '${activeRunId}')` : '') +
56
+ `. Refusing rather than running two turns of one conversation at once — ` +
57
+ `they would each answer from state the other is about to replace. ` +
58
+ `Wait for the active run, or build the standing agent with ` +
59
+ `onConcurrentInvoke: 'enqueue' to queue this turn behind it instead.`);
60
+ this.name = 'ConcurrentRunError';
61
+ this.sessionId = sessionId;
62
+ if (activeRunId !== undefined)
63
+ this.activeRunId = activeRunId;
64
+ }
65
+ }
66
+ exports.ConcurrentRunError = ConcurrentRunError;
67
+ /**
68
+ * Raised when a run paused to ask a person something and the reply cannot carry
69
+ * a pause.
70
+ *
71
+ * **The run did not fail.** A pause is unfinished work: the agent stopped to ask
72
+ * and is waiting for an answer. What cannot happen here is storing it — the
73
+ * `'conversation-v1'` envelope holds a conversation, and a paused run is a
74
+ * conversation plus an engine checkpoint. So nothing is written, and the
75
+ * session keeps exactly the conversation it had before this request.
76
+ */
77
+ class PauseNotCarriedError extends Error {
78
+ code = 'ERR_PAUSE_NOT_CARRIED';
79
+ /** The tool that asked, when the run recorded which one it was. */
80
+ toolName;
81
+ /** The session whose stored conversation was left untouched. */
82
+ sessionId;
83
+ constructor(toolName, sessionId) {
84
+ super(`[hosting] the run paused to ask a person about ` +
85
+ (toolName ? `'${toolName}'` : 'a tool') +
86
+ ` and this reply cannot carry a pause. ` +
87
+ `The run did not fail — it is unfinished, waiting on an answer. ` +
88
+ `Nothing was written: ` +
89
+ (sessionId ? `session '${sessionId}'` : 'the session') +
90
+ ` still holds the conversation it held before this request. ` +
91
+ `The 'conversation-v1' envelope stores a conversation; a paused run is a ` +
92
+ `conversation plus an engine checkpoint, and storing half of it would be worse ` +
93
+ `than storing none. Carry the pause yourself with agent.run() / agent.resume().`);
94
+ this.name = 'PauseNotCarriedError';
95
+ if (toolName !== undefined)
96
+ this.toolName = toolName;
97
+ if (sessionId !== undefined)
98
+ this.sessionId = sessionId;
99
+ }
100
+ }
101
+ exports.PauseNotCarriedError = PauseNotCarriedError;
102
+ /**
103
+ * Assert that a host can do something, and throw a corrective error naming the
104
+ * adapter when it cannot.
105
+ *
106
+ * This is the feature-detection law with teeth: capabilities are read, never
107
+ * assumed, and asking for one that is absent tells you which adapter you are
108
+ * actually holding rather than failing quietly somewhere downstream.
109
+ *
110
+ * @example
111
+ * requireCapability(host, 'streaming'); // throws unless this host streams
112
+ *
113
+ * // or branch instead of insisting:
114
+ * if (host.capabilities.includes('streaming')) { ... }
115
+ */
116
+ function requireCapability(host, capability) {
117
+ if (host.capabilities.includes(capability))
118
+ return;
119
+ const has = host.capabilities.length > 0 ? host.capabilities.join(', ') : 'none';
120
+ throw new Error(`[hosting] the '${host.name}' host does not support '${capability}'. ` +
121
+ `It reports: ${has}. Feature-detect with ` +
122
+ `host.capabilities.includes('${capability}') and fall back, or serve on a host ` +
123
+ `that has it — capabilities are read from the adapter, never assumed from its name.`);
124
+ }
125
+ exports.requireCapability = requireCapability;
126
+ //# 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,MAAa,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;AAdD,0CAcC;AAED;;;;;;;;GAQG;AACH,MAAa,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;AApBD,gDAoBC;AAED;;;;;;;;;GASG;AACH,MAAa,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;AAxBD,oDAwBC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,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;AATD,8CASC"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * agentfootprint/hosting — the two ports between an agent and the place it runs.
4
+ *
5
+ * An agent that answers one call in a script and an agent that has been up for
6
+ * a month differ in two things, and only two: something has to carry requests
7
+ * to it, and the conversation has to outlive the request. This subpath is those
8
+ * two things as **ports**, plus local adapters that prove the ports work, plus
9
+ * the composer that wires them together.
10
+ *
11
+ * `AgentHost` — something can call me.
12
+ * `SessionLifecycle` — the conversation outlives the request.
13
+ * `standingAgent` — hydrate → resume-or-fresh → persist → reply.
14
+ *
15
+ * ── Why the ports look like nothing in particular ────────────────────────────
16
+ * Deliberately. Not one field, name or assumption here comes from any hosting
17
+ * product, cloud or protocol. A port shaped around one provider's request
18
+ * envelope stops being a port and becomes that provider's SDK with extra steps,
19
+ * and every adapter after the first pays for the shortcut. So the ports carry
20
+ * an input, a reply, an optional session id, headers, a signal — the vocabulary
21
+ * every transport already has — and everything specific to one place you might
22
+ * deploy lives in the adapter for that place. `nodeHost` gets no special
23
+ * treatment: its paths, status codes and JSON body shape are all in
24
+ * `nodeHost.ts` and the port types do not know they exist. A test greps these
25
+ * source files for vendor names, crudely and on purpose.
26
+ *
27
+ * ── What ships here ──────────────────────────────────────────────────────────
28
+ * • `nodeHost({ port?, hostname?, invokePath?, healthPath? })` — plain
29
+ * `node:http`, zero dependencies. `POST /invoke`, `GET /health`, and
30
+ * Server-Sent Events when the caller asks for them.
31
+ * • `memorySessions()` — conversations in a Map, for tests and local dev.
32
+ * • `standingAgent({ agent, sessions, host })` — the composer.
33
+ * • `toEnvelope` / `readEnvelope` — pack a conversation, and refuse by name to
34
+ * unpack a format this runtime does not know.
35
+ * • `requireCapability` — feature-detection with teeth.
36
+ *
37
+ * @example An agent that stays up and remembers
38
+ * import { Agent } from 'agentfootprint';
39
+ * import { standingAgent, nodeHost, memorySessions } from 'agentfootprint/hosting';
40
+ *
41
+ * const handle = await standingAgent({
42
+ * agent: Agent.create({ provider, model }).system('You help customers.').build(),
43
+ * sessions: memorySessions(),
44
+ * host: nodeHost({ port: 8080 }),
45
+ * });
46
+ * process.on('SIGTERM', () => void handle.close());
47
+ */
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.PauseNotCarriedError = exports.ConcurrentRunError = exports.HostClosedError = exports.requireCapability = exports.standingAgent = exports.readEnvelope = exports.toEnvelope = exports.memorySessions = exports.nodeHost = void 0;
50
+ var nodeHost_js_1 = require("./nodeHost.js");
51
+ Object.defineProperty(exports, "nodeHost", { enumerable: true, get: function () { return nodeHost_js_1.nodeHost; } });
52
+ var memorySessions_js_1 = require("./memorySessions.js");
53
+ Object.defineProperty(exports, "memorySessions", { enumerable: true, get: function () { return memorySessions_js_1.memorySessions; } });
54
+ var envelope_js_1 = require("./envelope.js");
55
+ Object.defineProperty(exports, "toEnvelope", { enumerable: true, get: function () { return envelope_js_1.toEnvelope; } });
56
+ Object.defineProperty(exports, "readEnvelope", { enumerable: true, get: function () { return envelope_js_1.readEnvelope; } });
57
+ var standingAgent_js_1 = require("./standingAgent.js");
58
+ Object.defineProperty(exports, "standingAgent", { enumerable: true, get: function () { return standingAgent_js_1.standingAgent; } });
59
+ var errors_js_1 = require("./errors.js");
60
+ Object.defineProperty(exports, "requireCapability", { enumerable: true, get: function () { return errors_js_1.requireCapability; } });
61
+ Object.defineProperty(exports, "HostClosedError", { enumerable: true, get: function () { return errors_js_1.HostClosedError; } });
62
+ Object.defineProperty(exports, "ConcurrentRunError", { enumerable: true, get: function () { return errors_js_1.ConcurrentRunError; } });
63
+ Object.defineProperty(exports, "PauseNotCarriedError", { enumerable: true, get: function () { return errors_js_1.PauseNotCarriedError; } });
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hosting/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;;;AAEH,6CAAyC;AAAhC,uGAAA,QAAQ,OAAA;AAGjB,yDAAqD;AAA5C,mHAAA,cAAc,OAAA;AACvB,6CAAyD;AAAhD,yGAAA,UAAU,OAAA;AAAE,2GAAA,YAAY,OAAA;AACjC,uDAAmD;AAA1C,iHAAA,aAAa,OAAA;AAEtB,yCAKqB;AAJnB,8GAAA,iBAAiB,OAAA;AACjB,4GAAA,eAAe,OAAA;AACf,+GAAA,kBAAkB,OAAA;AAClB,iHAAA,oBAAoB,OAAA"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * hosting/memorySessions — conversations in a Map.
4
+ *
5
+ * The smallest thing that satisfies `SessionLifecycle`, for tests and for local
6
+ * development where a Redis is ceremony you have not earned yet. It is exactly
7
+ * as durable as the process: restart and every conversation is gone.
8
+ *
9
+ * That is not a shortcoming to apologise for, it is the point of the port. Swap
10
+ * this for a store that survives a restart and the standing agent is unchanged
11
+ * — which is also how the "a crashed process resumes the conversation" test is
12
+ * written: keep the store, throw away everything else, and watch the
13
+ * conversation come back through the envelope alone.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.memorySessions = void 0;
17
+ /**
18
+ * An in-process session store.
19
+ *
20
+ * @example
21
+ * const sessions = memorySessions();
22
+ * await standingAgent({ agent, sessions, host: nodeHost({ port: 8080 }) });
23
+ */
24
+ function memorySessions() {
25
+ const stored = new Map();
26
+ return {
27
+ hydrate: (sessionId) => Promise.resolve(stored.get(sessionId)),
28
+ persist: (sessionId, envelope) => {
29
+ stored.set(sessionId, envelope);
30
+ return Promise.resolve();
31
+ },
32
+ };
33
+ }
34
+ exports.memorySessions = memorySessions;
35
+ //# 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,SAAgB,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;AATD,wCASC"}