agents 0.20.0 → 0.20.1

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.
@@ -100,6 +100,33 @@ import {
100
100
  } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
101
101
  import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
102
102
 
103
+ //#region src/observability/tracing/tracer.d.ts
104
+ type InvocationScopeOptions = {
105
+ /**
106
+ * Open a scope even inside a live one, for work deliberately detached from
107
+ * the handler that started it — `ctx.waitUntil` bodies, queue drains — which
108
+ * runs on past that handler and must not be cut off with it.
109
+ */
110
+ readonly detached?: boolean;
111
+ };
112
+ /**
113
+ * Runs `body` as one traced invocation.
114
+ *
115
+ * Work that escapes its native invocation cannot be traced from the context it
116
+ * started in: the still-open span is force-closed against the invocation that
117
+ * owned that context, which reports a negative duration or `span_not_ended`.
118
+ * Spans opened with {@link SpanLifetime.boundToInvocation} inside this scope
119
+ * are therefore closed before `body` settles — but not one moment earlier, so
120
+ * everything that completes during the invocation (the normal case: a chat
121
+ * turn is awaited by the handler that received it) still records its finish
122
+ * attributes. A span truncated this way is marked
123
+ * `cloudflare.agents.span.truncated` rather than passing as complete.
124
+ */
125
+ declare function withInvocationScope<T>(
126
+ body: () => T,
127
+ options?: InvocationScopeOptions
128
+ ): T;
129
+ //#endregion
103
130
  //#region src/sub-routing.d.ts
104
131
  /**
105
132
  * URL segment marking a parent↔child boundary.
@@ -281,19 +308,6 @@ declare class WorkerTransport extends WebStandardStreamableHTTPServerTransport$1
281
308
  private _capturedInitializeParams?;
282
309
  private _userOnSessionInitialized?;
283
310
  private _bridgeInstalled;
284
- /**
285
- * Tracks keepalive interval cleanups so we can fire them eagerly when the
286
- * SDK closes the underlying SSE stream via `closeSSEStream(requestId)` or
287
- * `closeStandaloneSSEStream()`. Keyed by the JSON-RPC request id that
288
- * triggered the stream, or the sentinel for the standalone GET stream.
289
- */
290
- private readonly _keepaliveCleanups;
291
- /**
292
- * Most recent JSON-RPC request id seen on an incoming POST. Used to key
293
- * keepalive cleanups when the response is an SSE stream tied to that
294
- * request (so `closeSSEStream(id)` can find and clear the interval).
295
- */
296
- private _pendingRequestId?;
297
311
  /**
298
312
  * Request ids whose SSE stream was deliberately torn down via
299
313
  * `closeSSEStream`. The SDK's `send()` throws "No connection established"
@@ -314,8 +328,7 @@ declare class WorkerTransport extends WebStandardStreamableHTTPServerTransport$1
314
328
  /**
315
329
  * Top-level request entry point. Handles CORS preflight, restores any
316
330
  * persisted state on first invocation, then delegates to the SDK transport
317
- * and finally appends CORS headers + keepalive to whatever response comes
318
- * back.
331
+ * and finally appends CORS headers to whatever response comes back.
319
332
  */
320
333
  handleRequest(
321
334
  request: Request,
@@ -331,7 +344,6 @@ declare class WorkerTransport extends WebStandardStreamableHTTPServerTransport$1
331
344
  */
332
345
  private normalizeAllowHeader;
333
346
  closeSSEStream(requestId: RequestId): void;
334
- closeStandaloneSSEStream(): void;
335
347
  close(): Promise<void>;
336
348
  /**
337
349
  * Swallow two classes of message that would otherwise surface as
@@ -356,34 +368,6 @@ declare class WorkerTransport extends WebStandardStreamableHTTPServerTransport$1
356
368
  message: JSONRPCMessage$1,
357
369
  options?: TransportSendOptions$1
358
370
  ): Promise<void>;
359
- /**
360
- * If the response is an SSE stream, tee the body through a TransformStream
361
- * that injects a `: keepalive\n\n` comment frame every 25s. The interval
362
- * is cleared when the wrapped stream closes — which happens both when the
363
- * SDK ends the underlying stream naturally and when `closeSSEStream` is
364
- * called.
365
- *
366
- * Keepalive policy:
367
- * - POST response streams (`key` is a request id): always keepalive.
368
- * In-progress tool calls have no recovery path — if the stream drops
369
- * mid-execution the result is lost — so we keep it under the
370
- * Cloudflare edge ~5min idle watchdog.
371
- * - Standalone GET stream (`key === "_standalone"`): keepalive only
372
- * when no `eventStore` is configured. When resumability is enabled,
373
- * clients reconnect with `Last-Event-ID` after an idle drop, so we
374
- * skip the keepalive and let the DO hibernate.
375
- *
376
- * Uses the shared `sse-keepalive` constants so both this wrapper and
377
- * `McpAgent.serve()` write identical frames at the same cadence.
378
- * See cloudflare/agents#1583.
379
- */
380
- private withKeepalive;
381
- /**
382
- * Does the SDK transport have an `eventStore`? Reaches into the SDK's
383
- * private field because the option isn't surfaced on the public API —
384
- * we only need a yes/no for keepalive policy.
385
- */
386
- private eventStoreConfigured;
387
371
  private getCorsHeaders;
388
372
  private withCorsHeaders;
389
373
  private installOnSessionInitializedBridge;
@@ -6109,6 +6093,7 @@ export {
6109
6093
  createLegacyMcpHandler as en,
6110
6094
  Schedule as et,
6111
6095
  AgentToolProgress as f,
6096
+ withInvocationScope as fn,
6112
6097
  getAgentByName as ft,
6113
6098
  AgentToolRunPart as g,
6114
6099
  unstable_callable as gt,
@@ -6159,4 +6144,4 @@ export {
6159
6144
  EmailRoutingOptions as z,
6160
6145
  RPCClientTransportOptions as zt
6161
6146
  };
6162
- //# sourceMappingURL=agent-tool-types-Btk9ETS-.d.ts.map
6147
+ //# sourceMappingURL=agent-tool-types-BC-WFlsz.d.ts.map
@@ -24,7 +24,7 @@ import {
24
24
  w as RunAgentToolOptions,
25
25
  x as ChatCapableAgentClass,
26
26
  y as AgentToolStoredChunk
27
- } from "./agent-tool-types-Btk9ETS-.js";
27
+ } from "./agent-tool-types-BC-WFlsz.js";
28
28
  export {
29
29
  AGENT_TOOL_MILESTONE_PART,
30
30
  AGENT_TOOL_PROGRESS_PART,
@@ -4,7 +4,7 @@ import {
4
4
  o as AgentToolEventMessage,
5
5
  s as AgentToolEventState,
6
6
  y as AgentToolStoredChunk
7
- } from "./agent-tool-types-Btk9ETS-.js";
7
+ } from "./agent-tool-types-BC-WFlsz.js";
8
8
 
9
9
  //#region src/chat/agent-tools.d.ts
10
10
  type AgentToolProgressEmitResult = "emitted" | "coalesced" | "inactive";
@@ -130,4 +130,4 @@ export {
130
130
  interceptAgentToolBroadcast as s,
131
131
  AgentToolBroadcastHooks as t
132
132
  };
133
- //# sourceMappingURL=agent-tools-UuScsJg3.d.ts.map
133
+ //# sourceMappingURL=agent-tools-DeHe9Xov.d.ts.map
@@ -19,7 +19,7 @@ import {
19
19
  w as RunAgentToolOptions,
20
20
  x as ChatCapableAgentClass,
21
21
  y as AgentToolStoredChunk
22
- } from "./agent-tool-types-Btk9ETS-.js";
22
+ } from "./agent-tool-types-BC-WFlsz.js";
23
23
  import { Tool } from "ai";
24
24
 
25
25
  //#region src/agent-tools.d.ts
@@ -4,7 +4,7 @@ import {
4
4
  a as AgentToolEvent,
5
5
  o as AgentToolEventMessage,
6
6
  s as AgentToolEventState
7
- } from "../agent-tool-types-Btk9ETS-.js";
7
+ } from "../agent-tool-types-BC-WFlsz.js";
8
8
  import {
9
9
  n as ClientToolSchema,
10
10
  r as createToolsFromClientSchemas,
@@ -18,7 +18,7 @@ import {
18
18
  r as AgentToolProgressEmitResult,
19
19
  s as interceptAgentToolBroadcast,
20
20
  t as AgentToolBroadcastHooks
21
- } from "../agent-tools-UuScsJg3.js";
21
+ } from "../agent-tools-DeHe9Xov.js";
22
22
  import { JSONSchema7, UIMessage } from "ai";
23
23
  import { Connection } from "agents";
24
24
 
@@ -2,7 +2,7 @@ import {
2
2
  O as Agent,
3
3
  ct as SubAgentClass,
4
4
  lt as SubAgentStub
5
- } from "../agent-tool-types-Btk9ETS-.js";
5
+ } from "../agent-tool-types-BC-WFlsz.js";
6
6
  import { Lock, QueueEntry, StateAdapter } from "chat";
7
7
 
8
8
  //#region src/chat-sdk/agent.d.ts
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as Agent } from "./agent-tool-types-Btk9ETS-.js";
1
+ import { O as Agent } from "./agent-tool-types-BC-WFlsz.js";
2
2
  import {
3
3
  ClientParameters,
4
4
  Method,
@@ -0,0 +1,204 @@
1
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import * as cloudflareWorkers from "cloudflare:workers";
4
+ //#region src/observability/tracing/tracer.ts
5
+ /**
6
+ * Spans that asked to be invocation-bounded while a scope was active. Closed
7
+ * when that scope ends; see {@link withInvocationScope}.
8
+ */
9
+ const invocationScope = new AsyncLocalStorage();
10
+ /**
11
+ * Runs `body` as one traced invocation.
12
+ *
13
+ * Work that escapes its native invocation cannot be traced from the context it
14
+ * started in: the still-open span is force-closed against the invocation that
15
+ * owned that context, which reports a negative duration or `span_not_ended`.
16
+ * Spans opened with {@link SpanLifetime.boundToInvocation} inside this scope
17
+ * are therefore closed before `body` settles — but not one moment earlier, so
18
+ * everything that completes during the invocation (the normal case: a chat
19
+ * turn is awaited by the handler that received it) still records its finish
20
+ * attributes. A span truncated this way is marked
21
+ * `cloudflare.agents.span.truncated` rather than passing as complete.
22
+ */
23
+ function withInvocationScope(body, options) {
24
+ const current = invocationScope.getStore();
25
+ if (options?.detached !== true && current !== void 0 && !current.ended) return body();
26
+ const scope = {
27
+ ended: false,
28
+ open: /* @__PURE__ */ new Set()
29
+ };
30
+ const endInvocation = () => {
31
+ scope.ended = true;
32
+ for (const span of scope.open) try {
33
+ span.truncate();
34
+ } catch {}
35
+ scope.open.clear();
36
+ };
37
+ return invocationScope.run(scope, () => {
38
+ let result;
39
+ try {
40
+ result = body();
41
+ } catch (cause) {
42
+ endInvocation();
43
+ throw cause;
44
+ }
45
+ if (isPromiseLike(result)) return Promise.resolve(result).finally(endInvocation);
46
+ endInvocation();
47
+ return result;
48
+ });
49
+ }
50
+ /** Creates a tracer from a runtime span capability. */
51
+ function createTracer(runtime) {
52
+ return new RuntimeTracer(runtime);
53
+ }
54
+ var RuntimeTracer = class {
55
+ constructor(runtime) {
56
+ this.runtime = runtime;
57
+ }
58
+ withSpan(name, attributes, run, lifetime) {
59
+ return this.activate(name, attributes, (span) => {
60
+ const outOfBand = lifetime?.boundToInvocation === true && bindToInvocation(span);
61
+ const result = run(span);
62
+ if (isPromiseLike(result)) {
63
+ if (outOfBand) span.truncate();
64
+ return Promise.resolve(result).catch((cause) => {
65
+ span.fail(cause);
66
+ throw cause;
67
+ }).finally(() => {
68
+ span.close();
69
+ });
70
+ }
71
+ if (outOfBand) span.truncate();
72
+ span.close();
73
+ return result;
74
+ });
75
+ }
76
+ openSpan(name, attributes, activate, lifetime) {
77
+ if (!lifetime?.boundToInvocation) return this.activate(name, attributes, activate);
78
+ return this.activate(name, attributes, (span) => {
79
+ const invocationEnded = bindToInvocation(span);
80
+ try {
81
+ return activate(span);
82
+ } finally {
83
+ if (invocationEnded) span.truncate();
84
+ }
85
+ });
86
+ }
87
+ /**
88
+ * Shared scaffold: opens an active span, seeds its attributes, and fails the
89
+ * span on a thrown defect before rethrowing. The `body` decides the span's
90
+ * finishing policy (managed vs. caller-owned).
91
+ */
92
+ activate(name, attributes, body) {
93
+ return this.runtime.startActiveSpan(name, (writer) => {
94
+ setAttributes(writer, attributes);
95
+ const span = new ManagedSpan(writer);
96
+ try {
97
+ return body(span);
98
+ } catch (cause) {
99
+ span.fail(cause);
100
+ throw cause;
101
+ }
102
+ });
103
+ }
104
+ };
105
+ var _closed = /* @__PURE__ */ new WeakMap();
106
+ var ManagedSpan = class {
107
+ constructor(span) {
108
+ this.span = span;
109
+ _classPrivateFieldInitSpec(this, _closed, false);
110
+ }
111
+ get isTraced() {
112
+ return this.span.isTraced;
113
+ }
114
+ /** INTERNAL: see {@link writeSpanAttributes}. */
115
+ writeAttributes(attributes) {
116
+ if (_classPrivateFieldGet2(_closed, this)) return;
117
+ setAttributes(this.span, attributes);
118
+ }
119
+ finish(attributes = {}) {
120
+ if (_classPrivateFieldGet2(_closed, this)) return;
121
+ setAttributes(this.span, attributes);
122
+ this.close();
123
+ }
124
+ fail(cause) {
125
+ if (_classPrivateFieldGet2(_closed, this)) return;
126
+ if (isCancellation(cause)) setAttributes(this.span, { "cloudflare.agents.canceled": true });
127
+ else setAttributes(this.span, { "error.type": cause instanceof Error ? cause.name || "Error" : typeof cause });
128
+ this.close();
129
+ }
130
+ close() {
131
+ if (_classPrivateFieldGet2(_closed, this)) return;
132
+ _classPrivateFieldSet2(_closed, this, true);
133
+ this.span.end();
134
+ }
135
+ /**
136
+ * Closes a span whose work has not finished because its invocation is ending.
137
+ * The marker distinguishes "this span has no tokens because the turn escaped
138
+ * its invocation" from "this span completed and reported none".
139
+ */
140
+ truncate() {
141
+ if (_classPrivateFieldGet2(_closed, this)) return;
142
+ setAttributes(this.span, { "cloudflare.agents.span.truncated": true });
143
+ this.close();
144
+ }
145
+ };
146
+ /**
147
+ * Registers a span for closure at the end of the current invocation, and
148
+ * reports whether that invocation has already ended.
149
+ *
150
+ * Outside any scope the span keeps its natural lifetime: with no known
151
+ * invocation boundary there is nothing to bound it to, and closing early would
152
+ * discard finish attributes for no gain. Inside a scope that has already
153
+ * ended — work resumed later while still carrying this context — the span
154
+ * cannot outlive an invocation that is already gone, so the caller truncates
155
+ * it as soon as it has written what it knows.
156
+ */
157
+ function bindToInvocation(span) {
158
+ const scope = invocationScope.getStore();
159
+ if (scope === void 0) return false;
160
+ if (scope.ended) return true;
161
+ scope.open.add(span);
162
+ return false;
163
+ }
164
+ /**
165
+ * INTERNAL: writes attributes onto an open managed span. Lets instrumentation
166
+ * defer expensive attribute computation until after the isTraced check (span
167
+ * names must exist at open time; attributes need not). Not part of the public
168
+ * barrel surface.
169
+ */
170
+ function writeSpanAttributes(span, attributes) {
171
+ if (span instanceof ManagedSpan) span.writeAttributes(attributes);
172
+ }
173
+ function setAttributes(span, attributes) {
174
+ if (!span.isTraced) return;
175
+ try {
176
+ for (const [key, value] of Object.entries(attributes)) if (value !== void 0) span.setAttribute(key, value);
177
+ } catch {}
178
+ }
179
+ function isPromiseLike(value) {
180
+ return value !== null && value !== void 0 && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
181
+ }
182
+ /**
183
+ * Recognizes caller/runtime cancellation (an `AbortError`, e.g. from an aborted
184
+ * `AbortSignal`) so it can be classified separately from genuine failures. A
185
+ * `DOMException` named `AbortError` is not always an `Error` instance, so this
186
+ * probes the `name` field structurally rather than via `instanceof`.
187
+ */
188
+ function isCancellation(cause) {
189
+ return typeof cause === "object" && cause !== null && "name" in cause && cause.name === "AbortError";
190
+ }
191
+ //#endregion
192
+ //#region src/observability/tracing/cloudflare.ts
193
+ const noopSpan = {
194
+ isTraced: false,
195
+ setAttribute() {},
196
+ end() {}
197
+ };
198
+ const tracer = createTracer(cloudflareWorkers.tracing ?? { startActiveSpan(_name, run) {
199
+ return run(noopSpan);
200
+ } });
201
+ //#endregion
202
+ export { withInvocationScope as n, writeSpanAttributes as r, tracer as t };
203
+
204
+ //# sourceMappingURL=cloudflare-BduZwmYK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare-BduZwmYK.js","names":[],"sources":["../src/observability/tracing/tracer.ts","../src/observability/tracing/cloudflare.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/** Attribute values accepted by custom spans. */\nexport type TraceAttributeValue = string | number | boolean | undefined;\n\n/** Initial or finish attributes attached to a span. */\nexport type TraceAttributes = Readonly<Record<string, TraceAttributeValue>>;\n\n/** A value that may complete synchronously or through a promise-like result. */\nexport type MaybePromise<T> = T | PromiseLike<T>;\n\n/** Minimal runtime span surface used by tracers. */\nexport type SpanWriter = {\n readonly isTraced: boolean;\n setAttribute(key: string, value: TraceAttributeValue): void;\n end(): void;\n};\n\n/** Runtime capability for starting an active span in the current async context. */\nexport type SpanRuntime = {\n startActiveSpan<T>(name: string, run: (span: SpanWriter) => T): T;\n};\n\n/** Optional automatic lifetime policy for invocation-bounded spans. */\nexport type SpanLifetime = {\n /**\n * Close the span no later than the end of the surrounding\n * {@link withInvocationScope}, so it cannot outlive the native invocation\n * that owns its tracing context. Ignored outside such a scope.\n */\n readonly boundToInvocation?: boolean;\n};\n\n/** AgentTracer seam used by integrations. */\nexport type AgentTracer = {\n /**\n * Runs `run` inside an active span whose lifetime the tracer owns: the span\n * finishes when `run` returns (or its promise resolves) and fails when `run`\n * throws or rejects. Callers do not call {@link AgentSpan.finish}/{@link AgentSpan.fail};\n * doing so early is safe but the tracer guarantees closure.\n *\n * @template T The value produced by the instrumented work.\n */\n withSpan<T>(\n name: string,\n attributes: TraceAttributes,\n run: (span: AgentSpan) => MaybePromise<T>,\n lifetime?: SpanLifetime\n ): T | Promise<T>;\n /**\n * Activates a span and returns whatever `activate` returns (typically the\n * {@link AgentSpan} handle itself). The caller owns the span lifetime and MUST call\n * {@link AgentSpan.finish} or {@link AgentSpan.fail}; an unfinished span leaks. Use this\n * for work that outlives the callback, such as streams and event-driven\n * telemetry. A throw from `activate` still fails the span before rethrowing.\n *\n * @template T The value returned to the caller, usually the span handle.\n */\n openSpan<T>(\n name: string,\n attributes: TraceAttributes,\n activate: (span: AgentSpan) => T,\n lifetime?: SpanLifetime\n ): T;\n};\n\n/** Active span handle passed to instrumented work. */\nexport type AgentSpan = {\n /**\n * Whether this invocation is actually being traced. Instrumentation can use\n * this to skip expensive capture work when nobody is listening.\n */\n readonly isTraced: boolean;\n /** Records the optional finish attributes and ends the span. Idempotent. */\n finish(attributes?: TraceAttributes): void;\n /**\n * Ends the span as not-successful. Genuine failures record `error.type`;\n * recognized cancellations (an `AbortError`) record `canceled` instead so aborts\n * are not counted as errors. The cause message is never recorded. Idempotent.\n */\n fail(cause: unknown): void;\n};\n\ntype InvocationScope = {\n /** Bounded spans still open. Emptied when the invocation ends. */\n readonly open: Set<ManagedSpan>;\n /** Whether the invocation this scope represents has already ended. */\n ended: boolean;\n};\n\nexport type InvocationScopeOptions = {\n /**\n * Open a scope even inside a live one, for work deliberately detached from\n * the handler that started it — `ctx.waitUntil` bodies, queue drains — which\n * runs on past that handler and must not be cut off with it.\n */\n readonly detached?: boolean;\n};\n\n/**\n * Spans that asked to be invocation-bounded while a scope was active. Closed\n * when that scope ends; see {@link withInvocationScope}.\n */\nconst invocationScope = new AsyncLocalStorage<InvocationScope>();\n\n/**\n * Runs `body` as one traced invocation.\n *\n * Work that escapes its native invocation cannot be traced from the context it\n * started in: the still-open span is force-closed against the invocation that\n * owned that context, which reports a negative duration or `span_not_ended`.\n * Spans opened with {@link SpanLifetime.boundToInvocation} inside this scope\n * are therefore closed before `body` settles — but not one moment earlier, so\n * everything that completes during the invocation (the normal case: a chat\n * turn is awaited by the handler that received it) still records its finish\n * attributes. A span truncated this way is marked\n * `cloudflare.agents.span.truncated` rather than passing as complete.\n */\nexport function withInvocationScope<T>(\n body: () => T,\n options?: InvocationScopeOptions\n): T {\n // Nested scopes would truncate spans at an inner boundary that is not an\n // invocation edge, so the outermost live scope owns the lifetime. A scope\n // that has already ended is not an enclosing invocation at all — it is a\n // leftover context on work that resumed later — so `body` starts a new one.\n const current = invocationScope.getStore();\n if (options?.detached !== true && current !== undefined && !current.ended) {\n return body();\n }\n\n const scope: InvocationScope = { ended: false, open: new Set() };\n const endInvocation = () => {\n scope.ended = true;\n for (const span of scope.open) {\n // Fail open: this runs on the return path of every agent handler, so a\n // span writer that throws must not take the handler's result with it,\n // nor stop the remaining spans from closing.\n try {\n span.truncate();\n } catch {\n // Nothing useful to do with a broken span writer.\n }\n }\n scope.open.clear();\n };\n\n return invocationScope.run(scope, () => {\n let result: T;\n try {\n result = body();\n } catch (cause: unknown) {\n endInvocation();\n throw cause;\n }\n\n if (isPromiseLike(result)) {\n // SAFETY: T is promise-like here, so the settled promise is still a T.\n return Promise.resolve(result).finally(endInvocation) as T;\n }\n\n endInvocation();\n return result;\n });\n}\n\n/** Creates a tracer from a runtime span capability. */\nexport function createTracer(runtime: SpanRuntime): AgentTracer {\n return new RuntimeTracer(runtime);\n}\n\nclass RuntimeTracer implements AgentTracer {\n constructor(private readonly runtime: SpanRuntime) {}\n\n withSpan<T>(\n name: string,\n attributes: TraceAttributes,\n run: (span: AgentSpan) => MaybePromise<T>,\n lifetime?: SpanLifetime\n ): T | Promise<T> {\n return this.activate(name, attributes, (span) => {\n // Ask before running: a span that opens and closes in one tick still\n // needs to know whether that tick happened after its invocation ended.\n const outOfBand =\n lifetime?.boundToInvocation === true && bindToInvocation(span);\n const result = run(span);\n if (isPromiseLike(result)) {\n if (outOfBand) {\n span.truncate();\n }\n return Promise.resolve(result)\n .catch((cause: unknown) => {\n span.fail(cause);\n throw cause;\n })\n .finally(() => {\n span.close();\n });\n }\n\n if (outOfBand) {\n span.truncate();\n }\n span.close();\n return result;\n });\n }\n\n openSpan<T>(\n name: string,\n attributes: TraceAttributes,\n activate: (span: AgentSpan) => T,\n lifetime?: SpanLifetime\n ): T {\n if (!lifetime?.boundToInvocation) {\n return this.activate(name, attributes, activate);\n }\n\n return this.activate(name, attributes, (span) => {\n const invocationEnded = bindToInvocation(span);\n try {\n return activate(span);\n } finally {\n // Truncate only once `activate` has had its turn: callers that open\n // with an empty attribute set and write the real one lazily (the AI\n // operation span does, to stay cheap for untraced calls) would\n // otherwise be closed before writing anything at all.\n if (invocationEnded) {\n span.truncate();\n }\n }\n });\n }\n\n /**\n * Shared scaffold: opens an active span, seeds its attributes, and fails the\n * span on a thrown defect before rethrowing. The `body` decides the span's\n * finishing policy (managed vs. caller-owned).\n */\n private activate<T>(\n name: string,\n attributes: TraceAttributes,\n body: (span: ManagedSpan) => T\n ): T {\n return this.runtime.startActiveSpan(name, (writer) => {\n setAttributes(writer, attributes);\n const span = new ManagedSpan(writer);\n\n try {\n return body(span);\n } catch (cause: unknown) {\n span.fail(cause);\n throw cause;\n }\n });\n }\n}\n\nclass ManagedSpan implements AgentSpan {\n #closed = false;\n\n constructor(private readonly span: SpanWriter) {}\n\n get isTraced(): boolean {\n return this.span.isTraced;\n }\n\n /** INTERNAL: see {@link writeSpanAttributes}. */\n writeAttributes(attributes: TraceAttributes): void {\n if (this.#closed) {\n return;\n }\n\n setAttributes(this.span, attributes);\n }\n\n finish(attributes: TraceAttributes = {}): void {\n if (this.#closed) {\n return;\n }\n\n setAttributes(this.span, attributes);\n this.close();\n }\n\n fail(cause: unknown): void {\n if (this.#closed) {\n return;\n }\n\n if (isCancellation(cause)) {\n // Cancellation is a control path, not a failure: OTel semconv leaves\n // status Unset and records no error.type for cancellations, so aborted\n // operations do not inflate error rates. The vendor marker is additive.\n setAttributes(this.span, { \"cloudflare.agents.canceled\": true });\n } else {\n // Workers' custom Span API does not currently expose setStatus(). Do not\n // invent an `otel.status_code` attribute: status is span state in OTel,\n // not an attribute. error.type remains the standard queryable marker.\n setAttributes(this.span, {\n \"error.type\":\n cause instanceof Error ? cause.name || \"Error\" : typeof cause\n });\n }\n\n this.close();\n }\n\n close(): void {\n if (this.#closed) {\n return;\n }\n\n this.#closed = true;\n this.span.end();\n }\n\n /**\n * Closes a span whose work has not finished because its invocation is ending.\n * The marker distinguishes \"this span has no tokens because the turn escaped\n * its invocation\" from \"this span completed and reported none\".\n */\n truncate(): void {\n if (this.#closed) {\n return;\n }\n\n setAttributes(this.span, { \"cloudflare.agents.span.truncated\": true });\n this.close();\n }\n}\n\n/**\n * Registers a span for closure at the end of the current invocation, and\n * reports whether that invocation has already ended.\n *\n * Outside any scope the span keeps its natural lifetime: with no known\n * invocation boundary there is nothing to bound it to, and closing early would\n * discard finish attributes for no gain. Inside a scope that has already\n * ended — work resumed later while still carrying this context — the span\n * cannot outlive an invocation that is already gone, so the caller truncates\n * it as soon as it has written what it knows.\n */\nfunction bindToInvocation(span: ManagedSpan): boolean {\n const scope = invocationScope.getStore();\n if (scope === undefined) {\n return false;\n }\n\n if (scope.ended) {\n return true;\n }\n\n scope.open.add(span);\n return false;\n}\n\n/**\n * INTERNAL: writes attributes onto an open managed span. Lets instrumentation\n * defer expensive attribute computation until after the isTraced check (span\n * names must exist at open time; attributes need not). Not part of the public\n * barrel surface.\n */\nexport function writeSpanAttributes(\n span: AgentSpan,\n attributes: TraceAttributes\n): void {\n if (span instanceof ManagedSpan) {\n span.writeAttributes(attributes);\n }\n}\n\nfunction setAttributes(span: SpanWriter, attributes: TraceAttributes): void {\n if (!span.isTraced) {\n return;\n }\n\n // Fail-safe: a throwing writer must not leak the span or replace the\n // application's original error with a telemetry one.\n try {\n for (const [key, value] of Object.entries(attributes)) {\n if (value !== undefined) {\n span.setAttribute(key, value);\n }\n }\n } catch {\n // Drop the attributes; the span still closes.\n }\n}\n\nfunction isPromiseLike<T>(value: MaybePromise<T>): value is PromiseLike<T> {\n return (\n value !== null &&\n value !== undefined &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n \"then\" in value &&\n typeof value.then === \"function\"\n );\n}\n\n/**\n * Recognizes caller/runtime cancellation (an `AbortError`, e.g. from an aborted\n * `AbortSignal`) so it can be classified separately from genuine failures. A\n * `DOMException` named `AbortError` is not always an `Error` instance, so this\n * probes the `name` field structurally rather than via `instanceof`.\n */\nfunction isCancellation(cause: unknown): boolean {\n return (\n typeof cause === \"object\" &&\n cause !== null &&\n \"name\" in cause &&\n cause.name === \"AbortError\"\n );\n}\n","import * as cloudflareWorkers from \"cloudflare:workers\";\nimport { createTracer } from \"./tracer\";\nimport type { SpanRuntime, SpanWriter, AgentTracer } from \"./tracer\";\n\nconst noopSpan: SpanWriter = {\n isTraced: false,\n setAttribute() {},\n end() {}\n};\n\nconst noopRuntime: SpanRuntime = {\n startActiveSpan(_name, run) {\n return run(noopSpan);\n }\n};\n\n// Accessed via the namespace so runtimes that predate the `tracing` export\n// degrade to a no-op tracer instead of failing at module-link time — this\n// module loads with the main `agents` entry, not just for tracing users.\nconst runtime: SpanRuntime =\n (cloudflareWorkers as { tracing?: SpanRuntime }).tracing ?? noopRuntime;\n\nexport const tracer: AgentTracer = createTracer(runtime);\n"],"mappings":"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"}
@@ -1,37 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { WebStandardStreamableHTTPServerTransport, createMcpHandler, hostHeaderValidationResponse, isJSONRPCRequest, isLegacyRequest, localhostAllowedHostnames, localhostAllowedOrigins, originValidationResponse } from "@modelcontextprotocol/server";
3
- //#region src/mcp/sse-keepalive.ts
4
- /**
5
- * Shared SSE keepalive utility for MCP transports.
6
- *
7
- * Cloudflare's edge closes idle SSE responses after ~5 minutes. Writers
8
- * that may sit silent for that long (long-running tool calls, idle
9
- * standalone GET streams) arm a keepalive to keep the response under the
10
- * watchdog.
11
- *
12
- * See cloudflare/agents#1583.
13
- */
14
- /** Interval between SSE keepalive comment frames, in ms.
15
- *
16
- * The WHATWG SSE spec recommends a comment line every "15 seconds or so"
17
- * (html.spec.whatwg.org §9.2.7). 25s gives comfortable headroom below
18
- * both the ~30s post-handler background-work cancellation window on
19
- * Workers and the ~5min Cloudflare edge idle-stream watchdog.
20
- */
21
- const KEEPALIVE_INTERVAL_MS = 25e3;
22
- /** SSE comment frame the parser drops before any event dispatch. */
23
- const KEEPALIVE_FRAME = ": keepalive\n\n";
24
- /**
25
- * Start an SSE keepalive on `writer`. Returns a `clearInterval` handle
26
- * that the stream cleanup must invoke when the stream closes.
27
- */
28
- function startKeepalive(writer, encoder) {
29
- const handle = setInterval(() => {
30
- writer.write(encoder.encode(KEEPALIVE_FRAME)).catch(() => clearInterval(handle));
31
- }, KEEPALIVE_INTERVAL_MS);
32
- return handle;
33
- }
34
- //#endregion
35
3
  //#region src/mcp/auth-context.ts
36
4
  const VERIFIED_OAUTH_CONTEXT = Symbol.for("cloudflare.workers-oauth-provider.verified-context.v1");
37
5
  const authContextStorage = new AsyncLocalStorage();
@@ -109,13 +77,14 @@ function reportHandlerError(onerror, error) {
109
77
  * Local deltas from the upstream stateless fallback:
110
78
  *
111
79
  * - impossible stateless server-to-client requests fail immediately rather
112
- * than leaving the tool handler waiting for a session response;
113
- * - streamed POST responses receive Cloudflare's 25-second SSE keepalive.
80
+ * than leaving the tool handler waiting for a session response.
114
81
  *
115
- * Remove this adapter once the SDK exposes both policies directly.
82
+ * Streaming and keepalive behavior remain delegated to the SDK transport.
83
+ * Remove this adapter once the SDK exposes the reverse-request policy directly.
116
84
  */
117
- function createLegacyCompatibilityRequestHandler(factory, onerror) {
118
- const fetch = async (request, options) => {
85
+ function createLegacyCompatibilityRequestHandler(factory, handlerOptions = {}) {
86
+ const { keepAliveMs, onerror } = handlerOptions;
87
+ const fetch = async (request, requestOptions) => {
119
88
  if (request.method.toUpperCase() !== "POST") return Response.json({
120
89
  jsonrpc: "2.0",
121
90
  error: {
@@ -127,20 +96,21 @@ function createLegacyCompatibilityRequestHandler(factory, onerror) {
127
96
  if (request.signal.aborted) return new Response(null, { status: 499 });
128
97
  let product;
129
98
  let transport;
130
- let clearResponseKeepalive = () => {};
131
99
  let teardownPromise;
132
100
  const teardown = () => teardownPromise ??= (async () => {
133
- clearResponseKeepalive();
134
101
  await Promise.all([transport?.close().catch(() => {}), product?.close().catch(() => {})]);
135
102
  })();
136
103
  const onAbort = () => void teardown();
137
104
  try {
138
105
  product = await factory({
139
106
  era: "legacy",
140
- ...options?.authInfo !== void 0 && { authInfo: options.authInfo },
107
+ ...requestOptions?.authInfo !== void 0 && { authInfo: requestOptions.authInfo },
141
108
  requestInfo: request
142
109
  });
143
- transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
110
+ transport = new WebStandardStreamableHTTPServerTransport({
111
+ keepAliveMs,
112
+ sessionIdGenerator: void 0
113
+ });
144
114
  const send = transport.send.bind(transport);
145
115
  transport.send = async (message, sendOptions) => {
146
116
  if (isJSONRPCRequest(message)) {
@@ -163,8 +133,8 @@ function createLegacyCompatibilityRequestHandler(factory, onerror) {
163
133
  }
164
134
  request.signal.addEventListener("abort", onAbort, { once: true });
165
135
  const response = await transport.handleRequest(request, {
166
- ...options?.authInfo !== void 0 && { authInfo: options.authInfo },
167
- ...options?.parsedBody !== void 0 && { parsedBody: options.parsedBody }
136
+ ...requestOptions?.authInfo !== void 0 && { authInfo: requestOptions.authInfo },
137
+ ...requestOptions?.parsedBody !== void 0 && { parsedBody: requestOptions.parsedBody }
168
138
  });
169
139
  if (response.body === null || !response.headers.get("content-type")?.includes("text/event-stream")) {
170
140
  request.signal.removeEventListener("abort", onAbort);
@@ -172,44 +142,22 @@ function createLegacyCompatibilityRequestHandler(factory, onerror) {
172
142
  return response;
173
143
  }
174
144
  const reader = response.body.getReader();
175
- const encoder = new TextEncoder();
176
- let keepalive;
177
- const clearKeepalive = () => {
178
- if (keepalive !== void 0) {
179
- clearInterval(keepalive);
180
- keepalive = void 0;
181
- }
182
- };
183
- clearResponseKeepalive = clearKeepalive;
184
145
  const body = new ReadableStream({
185
- start(controller) {
186
- if (teardownPromise) return;
187
- keepalive = setInterval(() => {
188
- try {
189
- controller.enqueue(encoder.encode(KEEPALIVE_FRAME));
190
- } catch {
191
- clearKeepalive();
192
- }
193
- }, KEEPALIVE_INTERVAL_MS);
194
- },
195
146
  async pull(controller) {
196
147
  try {
197
148
  const { done, value } = await reader.read();
198
149
  if (done) {
199
- clearKeepalive();
200
150
  request.signal.removeEventListener("abort", onAbort);
201
151
  await teardown();
202
152
  controller.close();
203
153
  } else if (value !== void 0) controller.enqueue(value);
204
154
  } catch (error) {
205
- clearKeepalive();
206
155
  request.signal.removeEventListener("abort", onAbort);
207
156
  await teardown();
208
157
  controller.error(error);
209
158
  }
210
159
  },
211
160
  async cancel(reason) {
212
- clearKeepalive();
213
161
  request.signal.removeEventListener("abort", onAbort);
214
162
  await reader.cancel(reason).catch(() => {});
215
163
  await teardown();
@@ -224,7 +172,7 @@ function createLegacyCompatibilityRequestHandler(factory, onerror) {
224
172
  request.signal.removeEventListener("abort", onAbort);
225
173
  await teardown();
226
174
  reportHandlerError(onerror, error);
227
- return internalErrorResponse(requestIdFromParsedBody(options?.parsedBody));
175
+ return internalErrorResponse(requestIdFromParsedBody(requestOptions?.parsedBody));
228
176
  }
229
177
  };
230
178
  return { fetch };
@@ -309,7 +257,10 @@ function createStatelessMcpHandler(factory, options = {}) {
309
257
  ...sdkOptions,
310
258
  legacy: "reject"
311
259
  });
312
- const legacyCompatibilityHandler = legacy === "stateless" ? createLegacyCompatibilityRequestHandler(factory, sdkOptions.onerror) : void 0;
260
+ const legacyCompatibilityHandler = legacy === "stateless" ? createLegacyCompatibilityRequestHandler(factory, {
261
+ keepAliveMs: sdkOptions.keepAliveMs,
262
+ onerror: sdkOptions.onerror
263
+ }) : void 0;
313
264
  const serve = async (request, requestOptions, workerCtx) => {
314
265
  const requestUrl = new URL(request.url);
315
266
  if (requestUrl.pathname !== route) return withCors(new Response("Not Found", { status: 404 }), corsOptions);
@@ -362,6 +313,6 @@ function createStatelessMcpHandler(factory, options = {}) {
362
313
  });
363
314
  }
364
315
  //#endregion
365
- export { KEEPALIVE_INTERVAL_MS as a, KEEPALIVE_FRAME as i, getMcpAuthContext as n, startKeepalive as o, runWithAuthContext as r, createStatelessMcpHandler as t };
316
+ export { getMcpAuthContext as n, runWithAuthContext as r, createStatelessMcpHandler as t };
366
317
 
367
- //# sourceMappingURL=handler-stateless-8hQN_kC3.js.map
318
+ //# sourceMappingURL=handler-stateless-CIkKPETH.js.map