@myagentroam/agent 0.9.159 → 0.9.160

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 (128) hide show
  1. package/dist/agent-core/extensions/agents.d.ts +69 -0
  2. package/dist/agent-core/extensions/agents.js +160 -0
  3. package/dist/agent-core/extensions/contracts.d.ts +260 -0
  4. package/dist/agent-core/extensions/contracts.js +1 -0
  5. package/dist/agent-core/extensions/data.d.ts +84 -0
  6. package/dist/agent-core/extensions/data.js +83 -0
  7. package/dist/agent-core/extensions/environment.d.ts +27 -0
  8. package/dist/agent-core/extensions/environment.js +52 -0
  9. package/dist/agent-core/extensions/files.d.ts +64 -0
  10. package/dist/agent-core/extensions/files.js +26 -0
  11. package/dist/agent-core/extensions/hooks.d.ts +32 -0
  12. package/dist/agent-core/extensions/hooks.js +135 -0
  13. package/dist/agent-core/extensions/manager.d.ts +39 -0
  14. package/dist/agent-core/extensions/manager.js +458 -0
  15. package/dist/agent-core/extensions/mcp.d.ts +99 -0
  16. package/dist/agent-core/extensions/mcp.js +433 -0
  17. package/dist/agent-core/extensions/model-request.d.ts +60 -0
  18. package/dist/agent-core/extensions/model-request.js +167 -0
  19. package/dist/agent-core/extensions/model-stream.d.ts +9 -0
  20. package/dist/agent-core/extensions/model-stream.js +101 -0
  21. package/dist/agent-core/extensions/models.d.ts +43 -0
  22. package/dist/agent-core/extensions/models.js +1 -0
  23. package/dist/agent-core/extensions/network.d.ts +32 -0
  24. package/dist/agent-core/extensions/network.js +97 -0
  25. package/dist/agent-core/extensions/operation.d.ts +29 -0
  26. package/dist/agent-core/extensions/operation.js +98 -0
  27. package/dist/agent-core/extensions/processes.d.ts +52 -0
  28. package/dist/agent-core/extensions/processes.js +283 -0
  29. package/dist/agent-core/extensions/prompt-composition.d.ts +24 -0
  30. package/dist/agent-core/extensions/prompt-composition.js +106 -0
  31. package/dist/agent-core/extensions/prompt-input.d.ts +23 -0
  32. package/dist/agent-core/extensions/prompt-input.js +46 -0
  33. package/dist/agent-core/extensions/resources.d.ts +27 -0
  34. package/dist/agent-core/extensions/resources.js +221 -0
  35. package/dist/agent-core/extensions/services.d.ts +31 -0
  36. package/dist/agent-core/extensions/services.js +61 -0
  37. package/dist/agent-core/extensions/session-input.d.ts +151 -0
  38. package/dist/agent-core/extensions/session-input.js +351 -0
  39. package/dist/agent-core/extensions/session-messages.d.ts +41 -0
  40. package/dist/agent-core/extensions/session-messages.js +76 -0
  41. package/dist/agent-core/extensions/session.d.ts +68 -0
  42. package/dist/agent-core/extensions/session.js +593 -0
  43. package/dist/agent-core/extensions/settings.d.ts +59 -0
  44. package/dist/agent-core/extensions/settings.js +115 -0
  45. package/dist/agent-core/extensions/store.d.ts +10 -0
  46. package/dist/agent-core/extensions/store.js +131 -0
  47. package/dist/agent-core/extensions/tools.d.ts +61 -0
  48. package/dist/agent-core/extensions/tools.js +266 -0
  49. package/dist/agent-core/host/contracts.d.ts +25 -0
  50. package/dist/agent-core/host/local-agent-host.d.ts +10 -1
  51. package/dist/agent-core/host/local-agent-host.js +132 -20
  52. package/dist/agent-core/host/local-file-access.d.ts +30 -0
  53. package/dist/agent-core/host/local-file-access.js +107 -0
  54. package/dist/agent-core/host/local-network-access.d.ts +6 -0
  55. package/dist/agent-core/host/local-network-access.js +97 -0
  56. package/dist/agent-core/host/local-process-executor.d.ts +5 -1
  57. package/dist/agent-core/host/local-session-environment.d.ts +18 -0
  58. package/dist/agent-core/host/local-session-environment.js +102 -0
  59. package/dist/agent-core/model/anthropic-messages.js +73 -23
  60. package/dist/agent-core/model/contracts.d.ts +12 -0
  61. package/dist/agent-core/model/openai-responses.js +73 -12
  62. package/dist/agent-core/model/text-blocks.d.ts +7 -0
  63. package/dist/agent-core/model/text-blocks.js +40 -0
  64. package/dist/agent-core/prompts/index.d.ts +6 -2
  65. package/dist/agent-core/prompts/index.js +38 -24
  66. package/dist/agent-core/runtime/context/gc-tokens.js +6 -1
  67. package/dist/agent-core/runtime/context/normalize.js +21 -1
  68. package/dist/agent-core/runtime/context/projection.js +35 -1
  69. package/dist/agent-core/runtime/context/request-budget.js +5 -2
  70. package/dist/agent-core/runtime/context/restore.d.ts +5 -0
  71. package/dist/agent-core/runtime/context/restore.js +47 -4
  72. package/dist/agent-core/runtime/context/usage.js +2 -3
  73. package/dist/agent-core/runtime/contracts.d.ts +2 -0
  74. package/dist/agent-core/runtime/execution.js +5 -2
  75. package/dist/agent-core/sdk/agent.js +177 -40
  76. package/dist/agent-core/sdk/extension-agents.d.ts +9 -0
  77. package/dist/agent-core/sdk/extension-agents.js +51 -0
  78. package/dist/agent-core/sdk/extension-models.d.ts +13 -0
  79. package/dist/agent-core/sdk/extension-models.js +128 -0
  80. package/dist/agent-core/sdk/extension-session.d.ts +30 -0
  81. package/dist/agent-core/sdk/extension-session.js +103 -0
  82. package/dist/agent-core/sdk/extension-tools.d.ts +16 -0
  83. package/dist/agent-core/sdk/extension-tools.js +126 -0
  84. package/dist/agent-core/sdk/session.d.ts +26 -2
  85. package/dist/agent-core/sdk/session.js +1215 -506
  86. package/dist/agent-core/sdk/types.d.ts +3 -0
  87. package/dist/agent-core/session/context-gc-checkpoint.js +1 -1
  88. package/dist/agent-core/session/jsonl-store.d.ts +28 -8
  89. package/dist/agent-core/session/jsonl-store.js +124 -84
  90. package/dist/agent-core/session/types.d.ts +4 -1
  91. package/dist/agent-core/storage/file-lock.d.ts +4 -0
  92. package/dist/agent-core/storage/file-lock.js +83 -0
  93. package/dist/agent-core/subagent/contracts.d.ts +11 -1
  94. package/dist/agent-core/subagent/scheduler.d.ts +8 -0
  95. package/dist/agent-core/subagent/session-controller.d.ts +22 -1
  96. package/dist/agent-core/subagent/session-controller.js +355 -98
  97. package/dist/agent-core/tools/agent/agent-start.d.ts +1 -1
  98. package/dist/agent-core/tools/code-mode/cells.js +5 -15
  99. package/dist/agent-core/tools/code-mode/dispatch.d.ts +1 -0
  100. package/dist/agent-core/tools/code-mode/dispatch.js +13 -1
  101. package/dist/agent-core/tools/code-mode/index.js +1 -2
  102. package/dist/agent-core/tools/code-mode/observations.d.ts +5 -0
  103. package/dist/agent-core/tools/code-mode/observations.js +16 -5
  104. package/dist/agent-core/tools/code-mode/quickjs-engine.js +10 -5
  105. package/dist/agent-core/tools/execution.d.ts +11 -2
  106. package/dist/agent-core/tools/execution.js +58 -21
  107. package/dist/agent-core/tools/input.d.ts +2 -0
  108. package/dist/agent-core/tools/input.js +36 -0
  109. package/dist/agent-core/tools/local/apply-patch.js +21 -17
  110. package/dist/agent-core/tools/local/exec.d.ts +4 -4
  111. package/dist/agent-core/tools/local/exec.js +234 -62
  112. package/dist/agent-core/tools/local/registry.d.ts +6 -0
  113. package/dist/agent-core/tools/local/registry.js +60 -9
  114. package/dist/agent-core/tools/mcp/catalog.d.ts +6 -2
  115. package/dist/agent-core/tools/mcp/catalog.js +4 -4
  116. package/dist/agent-core/tools/mcp/history.js +2 -2
  117. package/dist/agent-core/tools/mcp/tool-search.d.ts +3 -1
  118. package/dist/agent-core/tools/mcp/tool-search.js +2 -2
  119. package/dist/agent-core/tools/output-projection.d.ts +3 -1
  120. package/dist/agent-core/tools/output-projection.js +11 -2
  121. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  122. package/dist/agent-core/tools/runtime.d.ts +3 -1
  123. package/dist/agent-core/tools/runtime.js +22 -7
  124. package/dist/agent-core/tools/scheduler.d.ts +2 -0
  125. package/dist/agent-core/tools/scheduler.js +43 -6
  126. package/dist/index.d.ts +21 -0
  127. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +5 -2
  128. package/package.json +1 -1
@@ -0,0 +1,433 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import { MarAgentError } from '../error.js';
5
+ import { assertMcpToolCatalog } from '../tools/mcp/catalog.js';
6
+ import { cloneCoreJson } from './data.js';
7
+ import { ExtensionResourceScope } from './resources.js';
8
+ /** One Session's declarations and owned Client leases feed its sole tool registry. */
9
+ export class ExtensionMcp {
10
+ tools;
11
+ hooks;
12
+ getTools;
13
+ toolRef;
14
+ isMcpRef;
15
+ invoke;
16
+ diagnose;
17
+ #declarations = [];
18
+ #serverRefs = new WeakMap();
19
+ #connectionRefs = new WeakMap();
20
+ #connections = new Set();
21
+ #calling = new AsyncLocalStorage();
22
+ constructor(tools, hooks, getTools, toolRef, isMcpRef, invoke, diagnose) {
23
+ this.tools = tools;
24
+ this.hooks = hooks;
25
+ this.getTools = getTools;
26
+ this.toolRef = toolRef;
27
+ this.isMcpRef = isMcpRef;
28
+ this.invoke = invoke;
29
+ this.diagnose = diagnose;
30
+ }
31
+ bind(owner, extensionId, backend, run) {
32
+ return {
33
+ register: (definition) => this.#register(owner, extensionId, definition),
34
+ servers: () => Object.freeze(this.#declarations
35
+ .filter((entry) => entry.owner === owner && this.#declared(entry))
36
+ .map((entry) => entry.ref)),
37
+ connections: () => Object.freeze([...this.#connections]
38
+ .filter((entry) => entry.owner === owner && !entry.closed)
39
+ .map((entry) => entry.ref)),
40
+ connect: async (input) => {
41
+ const request = captureInput(input, ['server', 'signal']);
42
+ return run((call) => this.hooks.invoke('mcp.connect', request, call, async (input, effective) => {
43
+ const server = this.#server(owner, input.server);
44
+ return this.#connect(owner, extensionId, server.ref, backend, effective);
45
+ }), request.signal);
46
+ },
47
+ catalog: async (input) => {
48
+ const request = captureInput(input, ['connection', 'signal']);
49
+ return run((call) => this.hooks.invoke('mcp.catalog', request, call, async (input, effective) => {
50
+ const connection = this.#connection(input.connection);
51
+ await this.#refresh(connection, effective);
52
+ const bindings = this.getTools(effective);
53
+ const result = Object.freeze({
54
+ revision: connection.catalog.revision,
55
+ servers: connection.catalog.servers,
56
+ tools: Object.freeze(connection.catalog.tools.flatMap((tool) => {
57
+ const binding = bindings.get(tool.definition.name);
58
+ return binding?.mcp && binding.mcp.sourceIdentity === tool.sourceIdentity
59
+ ? [this.toolRef(binding)]
60
+ : [];
61
+ }))
62
+ });
63
+ await this.hooks.emit('mcp.completed', {
64
+ operation: 'catalog',
65
+ connectionId: connection.ref.id,
66
+ toolCount: connection.catalog.tools.length
67
+ }, effective);
68
+ return result;
69
+ }), request.signal);
70
+ },
71
+ call: async (input) => {
72
+ const captured = captureInput(input, ['tool', 'arguments', 'callId', 'signal']);
73
+ let arguments_;
74
+ try {
75
+ arguments_ = structuredClone(captured.arguments);
76
+ }
77
+ catch {
78
+ throw new MarAgentError('MAR_AGENT_MCP_INPUT_INVALID', 'MCP arguments cannot be copied.');
79
+ }
80
+ return run(async (call) => {
81
+ if (!this.isMcpRef(captured.tool))
82
+ throw new MarAgentError('MAR_AGENT_MCP_REFERENCE_INVALID', 'MCP tool reference is invalid.');
83
+ return this.invoke({
84
+ name: captured.tool.definition.name,
85
+ ref: captured.tool,
86
+ arguments: arguments_,
87
+ ...(captured.callId === undefined ? {} : { callId: captured.callId })
88
+ }, call);
89
+ }, captured.signal);
90
+ },
91
+ disconnect: async (input) => {
92
+ const request = captureInput(input, ['connection', 'signal']);
93
+ return run((call) => this.hooks.invoke('mcp.disconnect', request, call, async (input, effective) => {
94
+ const connection = this.#connectionRefs.get(input.connection);
95
+ if (!connection)
96
+ throw new MarAgentError('MAR_AGENT_MCP_REFERENCE_INVALID', 'MCP connection reference is invalid.');
97
+ const active = this.#calling.getStore();
98
+ if (active?.active && active.connection === connection)
99
+ throw new MarAgentError('MAR_AGENT_MCP_CONNECTION_BUSY', 'MCP connection cannot close from its active call.');
100
+ await connection.registration.dispose();
101
+ await this.hooks.emit('mcp.completed', { operation: 'disconnect', connectionId: connection.ref.id }, effective);
102
+ }), request.signal);
103
+ }
104
+ };
105
+ }
106
+ servers() {
107
+ return [...this.#connections]
108
+ .filter((entry) => !entry.closed)
109
+ .flatMap((entry) => [...entry.catalog.servers]);
110
+ }
111
+ async #connect(owner, extensionId, server, backend, call) {
112
+ if (typeof backend.connect !== 'function')
113
+ throw new MarAgentError('MAR_AGENT_HOST_PORT_UNAVAILABLE', 'Dynamic MCP port is unavailable.');
114
+ let connection;
115
+ const registration = owner.effect(() => async () => {
116
+ if (connection)
117
+ await this.#close(connection);
118
+ });
119
+ await registration.ready;
120
+ try {
121
+ call.signal.throwIfAborted();
122
+ const lease = await backend.connect(server.definition, call);
123
+ if (!lease || typeof lease.close !== 'function')
124
+ throw new MarAgentError('MAR_AGENT_MCP_BACKEND_INVALID', 'MCP backend did not return a closeable Client lease.');
125
+ const scope = new ExtensionResourceScope();
126
+ scope.activate();
127
+ const ref = Object.freeze({
128
+ id: randomUUID(),
129
+ serverId: server.definition.id,
130
+ source: Object.freeze({
131
+ extensionId,
132
+ ownerInstanceId: owner.instanceId,
133
+ revision: randomUUID()
134
+ })
135
+ });
136
+ connection = {
137
+ owner,
138
+ ref,
139
+ backend: lease,
140
+ scope,
141
+ signal: owner.signal,
142
+ registration,
143
+ catalog: { revision: '', servers: [], tools: [] },
144
+ sources: [],
145
+ closed: false,
146
+ abort: () => {
147
+ void registration.dispose().catch(() => this.diagnose());
148
+ }
149
+ };
150
+ owner.signal.addEventListener('abort', connection.abort, { once: true });
151
+ if (call.signal !== owner.signal)
152
+ call.signal.addEventListener('abort', connection.abort, { once: true });
153
+ if (typeof lease.catalog !== 'function' || typeof lease.call !== 'function')
154
+ throw new MarAgentError('MAR_AGENT_MCP_BACKEND_INVALID', 'MCP Client lease is missing catalog or call.');
155
+ call.signal.throwIfAborted();
156
+ await this.#refresh(connection, call);
157
+ call.signal.throwIfAborted();
158
+ this.#connectionRefs.set(ref, connection);
159
+ this.#connections.add(connection);
160
+ await this.hooks.emit('mcp.completed', { operation: 'connect', connectionId: ref.id, toolCount: connection.catalog.tools.length }, call);
161
+ return ref;
162
+ }
163
+ catch (error) {
164
+ try {
165
+ await registration.dispose();
166
+ }
167
+ catch {
168
+ this.diagnose();
169
+ }
170
+ throw error;
171
+ }
172
+ finally {
173
+ // Only the owning scope, not a completed acquisition, controls the delivered lease.
174
+ if (connection && call.signal !== connection.signal)
175
+ call.signal.removeEventListener('abort', connection.abort);
176
+ }
177
+ }
178
+ #refresh(connection, call) {
179
+ const result = (connection.tail ?? Promise.resolve()).then(async () => {
180
+ if (connection.closed)
181
+ throw new MarAgentError('MAR_AGENT_MCP_REFERENCE_INVALID', 'MCP connection is unavailable.');
182
+ await runMcpConnection(connection, call, async (signal) => {
183
+ const raw = await connection.backend.catalog(Object.freeze({ ...call, signal }));
184
+ let catalog;
185
+ try {
186
+ catalog = structuredClone(raw);
187
+ }
188
+ catch {
189
+ throw new MarAgentError('MAR_AGENT_MCP_CATALOG_INVALID', 'MCP catalog cannot be copied.');
190
+ }
191
+ assertMcpToolCatalog(catalog, { nativeToolNames: true });
192
+ catalog = freeze({
193
+ ...catalog,
194
+ tools: catalog.tools.map((tool) => ({
195
+ ...tool,
196
+ definition: {
197
+ ...tool.definition,
198
+ allowedCallers: tool.definition.allowedCallers ?? ['direct', 'programmatic']
199
+ }
200
+ }))
201
+ });
202
+ signal.throwIfAborted();
203
+ if (isDeepStrictEqual(catalog, connection.catalog))
204
+ return;
205
+ const next = [];
206
+ const previous = connection.sources;
207
+ try {
208
+ for (const tool of catalog.tools) {
209
+ let source;
210
+ const binding = Object.freeze({
211
+ definition: tool.definition,
212
+ mcp: tool,
213
+ source: Object.freeze({
214
+ kind: 'mcp',
215
+ id: tool.sourceIdentity,
216
+ revision: tool.definitionRevision,
217
+ instanceId: connection.owner.instanceId
218
+ }),
219
+ registered: () => source.live,
220
+ available: () => source.live &&
221
+ !connection.closed &&
222
+ connection.owner.state !== 'RETIRING' &&
223
+ connection.owner.state !== 'DISPOSED',
224
+ invoke: async (input, operation) => {
225
+ if (!source.live || connection.closed)
226
+ throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'MCP tool source is unavailable.');
227
+ const frame = { connection, active: true };
228
+ const result = await runMcpConnection(connection, operation, async (signal) => {
229
+ signal.throwIfAborted();
230
+ if (!source.live)
231
+ throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'MCP tool source is unavailable.');
232
+ return this.#calling.run(frame, async () => {
233
+ try {
234
+ return await connection.backend.call({
235
+ name: tool.definition.name,
236
+ serverId: tool.serverId,
237
+ rawName: tool.rawName,
238
+ arguments: input.arguments,
239
+ ...(input.callId === undefined ? {} : { callId: input.callId }),
240
+ expectedTool: {
241
+ sourceIdentity: tool.sourceIdentity,
242
+ definitionRevision: tool.definitionRevision
243
+ }
244
+ }, Object.freeze({ ...operation, signal }));
245
+ }
246
+ finally {
247
+ frame.active = false;
248
+ }
249
+ });
250
+ });
251
+ await this.hooks.emit('mcp.completed', {
252
+ operation: 'call',
253
+ connectionId: connection.ref.id,
254
+ toolName: tool.definition.name
255
+ }, operation);
256
+ return result;
257
+ }
258
+ });
259
+ source = {
260
+ binding,
261
+ live: false,
262
+ registration: this.tools.contributeSource(connection.owner, binding)
263
+ };
264
+ next.push(source);
265
+ }
266
+ await Promise.all(next.map((source) => source.registration.ready));
267
+ signal.throwIfAborted();
268
+ for (const source of previous)
269
+ source.live = false;
270
+ for (const source of next)
271
+ source.live = true;
272
+ // Validation is synchronous: no other operation observes the tentative catalog.
273
+ try {
274
+ this.getTools(call);
275
+ }
276
+ catch (error) {
277
+ for (const source of previous)
278
+ source.live = true;
279
+ for (const source of next)
280
+ source.live = false;
281
+ throw error;
282
+ }
283
+ connection.sources = next;
284
+ connection.catalog = catalog;
285
+ }
286
+ catch (error) {
287
+ for (const source of next)
288
+ source.live = false;
289
+ await Promise.all(next.map((source) => source.registration.dispose()));
290
+ throw error;
291
+ }
292
+ await Promise.all(previous.map((source) => source.registration.dispose()));
293
+ });
294
+ });
295
+ connection.tail = result.catch(() => undefined);
296
+ return result;
297
+ }
298
+ #close(connection) {
299
+ if (connection.closing)
300
+ return connection.closing;
301
+ connection.closed = true;
302
+ this.#connections.delete(connection);
303
+ connection.signal.removeEventListener('abort', connection.abort);
304
+ connection.scope.retire();
305
+ for (const source of connection.sources)
306
+ source.live = false;
307
+ connection.closing = (async () => {
308
+ // Close the Client concurrently so non-cooperative transport waits can settle.
309
+ const results = await Promise.allSettled([
310
+ Promise.resolve().then(() => connection.backend.close()),
311
+ connection.scope.dispose(),
312
+ ...connection.sources.map((source) => source.registration.dispose())
313
+ ]);
314
+ const failure = results.find((result) => result.status === 'rejected');
315
+ if (failure?.status === 'rejected') {
316
+ if (failure.reason instanceof MarAgentError)
317
+ throw failure.reason;
318
+ throw new MarAgentError('MAR_AGENT_MCP_CLEANUP_FAILED', 'MCP Client lease did not close.', {
319
+ cause: failure.reason
320
+ });
321
+ }
322
+ })();
323
+ return connection.closing;
324
+ }
325
+ #register(owner, extensionId, input) {
326
+ const { id, configuration } = captureInput(input, ['id', 'configuration']);
327
+ if (typeof id !== 'string' || !id.trim())
328
+ throw new MarAgentError('MAR_AGENT_MCP_DEFINITION_INVALID', 'MCP declaration identity is invalid.');
329
+ let copied;
330
+ try {
331
+ copied = cloneCoreJson(configuration);
332
+ }
333
+ catch {
334
+ throw new MarAgentError('MAR_AGENT_MCP_DEFINITION_INVALID', 'MCP configuration must be serializable JSON.');
335
+ }
336
+ if (this.#declarations.some((entry) => this.#declared(entry) && entry.ref.definition.id === id && entry.owner !== owner))
337
+ throw new MarAgentError('MAR_AGENT_MCP_DEFINITION_CONFLICT', 'MCP declaration identity conflicts.');
338
+ const ref = Object.freeze({
339
+ definition: freeze({ id, configuration: copied }),
340
+ source: Object.freeze({
341
+ extensionId,
342
+ ownerInstanceId: owner.instanceId,
343
+ revision: randomUUID()
344
+ })
345
+ });
346
+ const entry = { owner, ref, live: true };
347
+ const registration = owner.effect(() => () => {
348
+ entry.live = false;
349
+ const index = this.#declarations.indexOf(entry);
350
+ if (index !== -1)
351
+ this.#declarations.splice(index, 1);
352
+ });
353
+ this.#declarations.push(entry);
354
+ this.#serverRefs.set(ref, entry);
355
+ return Object.freeze({ ...registration, ref });
356
+ }
357
+ #declared(entry) {
358
+ return (entry.live &&
359
+ (entry.owner.state === 'ACTIVE' ||
360
+ (entry.owner.state === 'STAGED' && ExtensionResourceScope.initializing)));
361
+ }
362
+ #server(owner, ref) {
363
+ const entry = typeof ref === 'string'
364
+ ? this.#declarations.findLast((entry) => entry.owner === owner && entry.ref.definition.id === ref && this.#declared(entry))
365
+ : this.#serverRefs.get(ref);
366
+ if (!entry || !this.#declared(entry))
367
+ throw new MarAgentError('MAR_AGENT_MCP_REFERENCE_INVALID', 'MCP declaration is unavailable.');
368
+ return entry;
369
+ }
370
+ #connection(ref) {
371
+ const connection = this.#connectionRefs.get(ref);
372
+ if (!connection || connection.closed)
373
+ throw new MarAgentError('MAR_AGENT_MCP_REFERENCE_INVALID', 'MCP connection is unavailable.');
374
+ return connection;
375
+ }
376
+ }
377
+ /** Project the same captured bindings used by invocation; never rediscover a backend by name. */
378
+ export function coreMcpCatalog(servers, bindings) {
379
+ const descriptions = new Map();
380
+ for (const server of servers) {
381
+ const previous = descriptions.get(server.id);
382
+ if (previous && !isDeepStrictEqual(previous, server))
383
+ throw new MarAgentError('MAR_AGENT_MCP_CATALOG_INVALID', 'MCP server identities conflict.');
384
+ descriptions.set(server.id, server);
385
+ }
386
+ const tools = [...bindings.values()]
387
+ .filter((binding) => binding.mcp !== undefined)
388
+ .map((binding) => ({
389
+ ...binding.mcp,
390
+ definition: binding.definition,
391
+ sourceIdentity: binding.source.kind === 'mcp'
392
+ ? binding.mcp.sourceIdentity
393
+ : digest([binding.source.id, binding.mcp.sourceIdentity]),
394
+ definitionRevision: binding.source.kind === 'mcp'
395
+ ? binding.mcp.definitionRevision
396
+ : digest([binding.source.revision, binding.mcp.definitionRevision, binding.definition])
397
+ }));
398
+ const catalog = { servers: [...descriptions.values()], tools };
399
+ return {
400
+ ...catalog,
401
+ revision: createHash('sha256').update(JSON.stringify(catalog)).digest('hex')
402
+ };
403
+ }
404
+ async function runMcpConnection(connection, call, operation) {
405
+ try {
406
+ return await connection.scope.run(operation, call.signal);
407
+ }
408
+ catch (error) {
409
+ // A Client may wrap the abort reason. The owned lease, not transport error identity,
410
+ // decides whether teardown cancelled this operation; preserve the original cause.
411
+ if (connection.scope.signal.aborted || connection.owner.signal.aborted)
412
+ throw new MarAgentError('MAR_AGENT_CANCELLED', 'MCP operation was cancelled.', {
413
+ cause: error
414
+ });
415
+ throw error;
416
+ }
417
+ }
418
+ function captureInput(input, keys) {
419
+ if (!input || typeof input !== 'object' || Array.isArray(input))
420
+ throw new MarAgentError('MAR_AGENT_MCP_INPUT_INVALID', 'MCP input must be an object.');
421
+ return Object.freeze(Object.fromEntries(keys.map((key) => [key, input[key]])));
422
+ }
423
+ function digest(value) {
424
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
425
+ }
426
+ function freeze(value) {
427
+ if (value && typeof value === 'object') {
428
+ for (const item of Object.values(value))
429
+ freeze(item);
430
+ Object.freeze(value);
431
+ }
432
+ return value;
433
+ }
@@ -0,0 +1,60 @@
1
+ import type { MarAgentReasoningEffort } from '../model/configuration.js';
2
+ import type { ModelRequest, ModelTextBlock } from '../model/contracts.js';
3
+ import type { ClientToolDefinition } from '../tools/contracts.js';
4
+ /** Provider-private context is retained through opaque references, never exposed as payloads. */
5
+ export type CoreModelMessage = {
6
+ readonly role: 'opaque';
7
+ readonly sourceRef: string;
8
+ } | {
9
+ readonly role: 'user' | 'assistant' | 'tool' | 'tool_call';
10
+ readonly content: string;
11
+ readonly textBlocks?: readonly ModelTextBlock[];
12
+ readonly sourceRef?: string;
13
+ readonly callId?: string;
14
+ readonly name?: string;
15
+ readonly arguments?: unknown;
16
+ readonly images?: readonly {
17
+ readonly mimeType: string;
18
+ readonly dataBase64: string;
19
+ }[];
20
+ };
21
+ export interface CoreModelRequest {
22
+ readonly modelId: string;
23
+ readonly system: string;
24
+ readonly systemBlocks?: readonly ModelTextBlock[];
25
+ readonly messages: readonly CoreModelMessage[];
26
+ readonly tools: readonly ClientToolDefinition[];
27
+ readonly toolChoice?: 'auto' | 'none';
28
+ readonly reasoningEffort?: MarAgentReasoningEffort;
29
+ readonly images?: readonly {
30
+ readonly mimeType: string;
31
+ readonly dataBase64: string;
32
+ }[];
33
+ }
34
+ export interface CoreRequestSnapshot {
35
+ readonly id: string;
36
+ readonly request: CoreModelRequest;
37
+ }
38
+ /** Only actual SDK model dispatch records snapshots; public views carry no raw payload authority. */
39
+ export declare class ExtensionRequestSnapshots {
40
+ #private;
41
+ record(modelId: string, request: ModelRequest): CoreRequestSnapshot;
42
+ complete(snapshot: CoreRequestSnapshot): void;
43
+ capture(input?: {
44
+ readonly completed?: boolean;
45
+ }): CoreRequestSnapshot | undefined;
46
+ resolve(snapshot: CoreRequestSnapshot): {
47
+ modelId: string;
48
+ request: ModelRequest;
49
+ view: CoreModelRequest;
50
+ };
51
+ }
52
+ /** One conversion per actual request; references are valid only for this candidate. */
53
+ export declare class ExtensionRequestBridge {
54
+ #private;
55
+ readonly modelId: string;
56
+ readonly original: ModelRequest;
57
+ readonly view: CoreModelRequest;
58
+ constructor(modelId: string, original: ModelRequest, sourceMessages?: readonly CoreModelMessage[]);
59
+ lower(view: CoreModelRequest): ModelRequest;
60
+ }
@@ -0,0 +1,167 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { resolveModelTextBlocks } from '../model/text-blocks.js';
4
+ import { MarAgentError } from '../error.js';
5
+ /** Only actual SDK model dispatch records snapshots; public views carry no raw payload authority. */
6
+ export class ExtensionRequestSnapshots {
7
+ #requests = new WeakMap();
8
+ #current;
9
+ #completed;
10
+ record(modelId, request) {
11
+ const { onAttemptDiagnostic: _diagnostic, ...serializable } = request;
12
+ const retained = structuredClone(serializable);
13
+ let view;
14
+ // Preserve dispatch-time isolation without projecting unused public views.
15
+ const snapshot = Object.freeze({
16
+ id: randomUUID(),
17
+ get request() {
18
+ return (view ??= freezeSnapshot(new ExtensionRequestBridge(modelId, retained).view));
19
+ }
20
+ });
21
+ this.#requests.set(snapshot, { modelId, request: retained });
22
+ this.#current = snapshot;
23
+ return snapshot;
24
+ }
25
+ complete(snapshot) {
26
+ if (!this.#requests.has(snapshot))
27
+ throw new MarAgentError('MAR_AGENT_MODEL_SNAPSHOT_INVALID', 'Request snapshot is unavailable.');
28
+ this.#completed = snapshot;
29
+ }
30
+ capture(input = {}) {
31
+ if (!input ||
32
+ typeof input !== 'object' ||
33
+ Array.isArray(input) ||
34
+ (input.completed !== undefined && typeof input.completed !== 'boolean'))
35
+ throw new MarAgentError('MAR_AGENT_MODEL_SNAPSHOT_INVALID', 'Request snapshot selection is invalid.');
36
+ return input.completed === true ? this.#completed : this.#current;
37
+ }
38
+ resolve(snapshot) {
39
+ const entry = this.#requests.get(snapshot);
40
+ if (!entry)
41
+ throw new MarAgentError('MAR_AGENT_MODEL_SNAPSHOT_INVALID', 'Request snapshot does not belong to the current Session.');
42
+ return {
43
+ modelId: entry.modelId,
44
+ request: structuredClone(entry.request),
45
+ view: snapshot.request
46
+ };
47
+ }
48
+ }
49
+ function freezeSnapshot(value) {
50
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
51
+ for (const child of Object.values(value))
52
+ freezeSnapshot(child);
53
+ Object.freeze(value);
54
+ }
55
+ return value;
56
+ }
57
+ /** One conversion per actual request; references are valid only for this candidate. */
58
+ export class ExtensionRequestBridge {
59
+ modelId;
60
+ original;
61
+ #messages = new Map();
62
+ view;
63
+ constructor(modelId, original, sourceMessages) {
64
+ this.modelId = modelId;
65
+ this.original = original;
66
+ const messages = original.messages.map((original, index) => {
67
+ const sourceRef = sourceMessages?.[index]?.sourceRef ?? randomUUID();
68
+ const view = original.role === 'provider'
69
+ ? { role: 'opaque', sourceRef }
70
+ : {
71
+ role: original.role,
72
+ content: original.content,
73
+ ...(original.textBlocks === undefined
74
+ ? {}
75
+ : { textBlocks: structuredClone(original.textBlocks) }),
76
+ sourceRef,
77
+ ...(original.callId !== undefined ? { callId: original.callId } : {}),
78
+ ...(original.name !== undefined ? { name: original.name } : {}),
79
+ ...(original.arguments !== undefined
80
+ ? { arguments: structuredClone(original.arguments) }
81
+ : {}),
82
+ ...(original.images !== undefined ? { images: structuredClone(original.images) } : {})
83
+ };
84
+ this.#messages.set(sourceRef, { original, view: structuredClone(view) });
85
+ return view;
86
+ });
87
+ this.view = {
88
+ modelId,
89
+ system: original.system,
90
+ ...(original.systemBlocks === undefined
91
+ ? {}
92
+ : { systemBlocks: structuredClone(original.systemBlocks) }),
93
+ messages,
94
+ tools: structuredClone(original.tools),
95
+ ...(original.toolChoice ? { toolChoice: original.toolChoice } : {}),
96
+ ...(original.reasoningEffort ? { reasoningEffort: original.reasoningEffort } : {}),
97
+ ...(original.images ? { images: structuredClone(original.images) } : {})
98
+ };
99
+ }
100
+ lower(view) {
101
+ if (!view ||
102
+ view.modelId !== this.modelId ||
103
+ typeof view.system !== 'string' ||
104
+ !Array.isArray(view.messages) ||
105
+ !Array.isArray(view.tools) ||
106
+ view.tools.some((tool) => !tool ||
107
+ typeof tool !== 'object' ||
108
+ typeof tool.name !== 'string' ||
109
+ typeof tool.description !== 'string' ||
110
+ !tool.inputSchema ||
111
+ typeof tool.inputSchema !== 'object' ||
112
+ Array.isArray(tool.inputSchema)))
113
+ throw new MarAgentError('MAR_AGENT_EXTENSION_MODEL_REQUEST_INVALID', 'Model request view is invalid.');
114
+ const seen = new Set();
115
+ const messages = view.messages.map((message) => {
116
+ if (!message || typeof message !== 'object')
117
+ throw new MarAgentError('MAR_AGENT_EXTENSION_MODEL_REQUEST_INVALID', 'Message view is invalid.');
118
+ const source = message.sourceRef ? this.#messages.get(message.sourceRef) : undefined;
119
+ if (message.sourceRef) {
120
+ if (!source || seen.has(message.sourceRef))
121
+ throw new MarAgentError('MAR_AGENT_EXTENSION_MODEL_REQUEST_INVALID', 'Message source reference is invalid.');
122
+ seen.add(message.sourceRef);
123
+ }
124
+ if (source && isDeepStrictEqual(message, source.view))
125
+ return source.original;
126
+ if (message.role === 'opaque' ||
127
+ typeof message.content !== 'string' ||
128
+ !['user', 'assistant', 'tool', 'tool_call'].includes(message.role))
129
+ throw new MarAgentError('MAR_AGENT_EXTENSION_MODEL_REQUEST_INVALID', 'Message view is invalid.');
130
+ if (message.textBlocks !== undefined && message.role === 'tool_call')
131
+ throw new MarAgentError('MAR_AGENT_MODEL_CAPABILITY_UNAVAILABLE', 'Text blocks cannot replace structured tool-call arguments.');
132
+ return {
133
+ role: message.role,
134
+ content: message.content,
135
+ ...(message.textBlocks === undefined
136
+ ? {}
137
+ : { textBlocks: resolveModelTextBlocks(message.content, message.textBlocks) }),
138
+ ...(message.callId !== undefined ? { callId: message.callId } : {}),
139
+ ...(message.name !== undefined ? { name: message.name } : {}),
140
+ ...(message.role === 'tool' &&
141
+ source?.original.role === 'tool' &&
142
+ message.callId === source.original.callId &&
143
+ message.name === source.original.name &&
144
+ source.original.isError !== undefined
145
+ ? { isError: source.original.isError }
146
+ : {}),
147
+ ...(message.arguments !== undefined
148
+ ? { arguments: structuredClone(message.arguments) }
149
+ : {}),
150
+ ...(message.images !== undefined ? { images: structuredClone(message.images) } : {})
151
+ };
152
+ });
153
+ const { toolChoice: _toolChoice, reasoningEffort: _reasoningEffort, images: _images, systemBlocks: _systemBlocks, ...retained } = this.original;
154
+ return {
155
+ ...retained,
156
+ system: view.system,
157
+ ...(view.systemBlocks === undefined
158
+ ? {}
159
+ : { systemBlocks: resolveModelTextBlocks(view.system, view.systemBlocks) }),
160
+ messages,
161
+ tools: structuredClone(view.tools),
162
+ ...(view.toolChoice ? { toolChoice: view.toolChoice } : {}),
163
+ ...(view.reasoningEffort ? { reasoningEffort: view.reasoningEffort } : {}),
164
+ ...(view.images ? { images: structuredClone(view.images) } : {})
165
+ };
166
+ }
167
+ }
@@ -0,0 +1,9 @@
1
+ import type { ModelEvent } from '../model/contracts.js';
2
+ export type CoreModelEvent = Exclude<ModelEvent, {
3
+ type: 'response.item';
4
+ }> | {
5
+ readonly type: 'opaque';
6
+ readonly sourceRef: string;
7
+ };
8
+ /** Pull-through transformation: no speculative reads, background producer, or stream replay. */
9
+ export declare function transformCoreModelStream(start: (signal: AbortSignal) => AsyncIterable<ModelEvent>, parent: AbortSignal, transform: (event: CoreModelEvent, signal: AbortSignal) => Promise<CoreModelEvent | undefined>, diagnoseCleanup?: () => void): AsyncIterable<ModelEvent>;