feat(planning): grille hebdomadaire complète avec API et filtres
- Connexion API via proxy Angular (résolution CORS, base path /api) - Import CSS ng-zorro global pour les modales et composants - Filtres Camion/Show câblés sur l'affichage de la grille - Camions affichés via TrucksService (linkés au show du même créneau) - Panneau de détails : spectacles + camions du jour sélectionné - Modale de création de spectacle stylisée avec fond et centrage - Positionnement précis des events à la minute dans leur créneau - Auto-scroll vers l'heure courante au chargement - Ligne "maintenant" sur la colonne du jour actuel - Régénération des services OpenAPI (nouveaux noms de types) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+94
-196
@@ -1,13 +1,12 @@
|
||||
import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
|
||||
import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
|
||||
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, JSONRPCMessageSchema } from "../types.js";
|
||||
import { auth, extractResourceMetadataUrl, UnauthorizedError } from "./auth.js";
|
||||
import { EventSourceParserStream } from "eventsource-parser/stream";
|
||||
// Default reconnection options for StreamableHTTP connections
|
||||
const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
|
||||
initialReconnectionDelay: 1000,
|
||||
maxReconnectionDelay: 30000,
|
||||
reconnectionDelayGrowFactor: 1.5,
|
||||
maxRetries: 2
|
||||
maxRetries: 2,
|
||||
};
|
||||
export class StreamableHTTPError extends Error {
|
||||
constructor(code, message) {
|
||||
@@ -22,77 +21,72 @@ export class StreamableHTTPError extends Error {
|
||||
*/
|
||||
export class StreamableHTTPClientTransport {
|
||||
constructor(url, opts) {
|
||||
this._hasCompletedAuthFlow = false; // Circuit breaker: detect auth success followed by immediate 401
|
||||
var _a;
|
||||
this._url = url;
|
||||
this._resourceMetadataUrl = undefined;
|
||||
this._scope = undefined;
|
||||
this._requestInit = opts?.requestInit;
|
||||
this._authProvider = opts?.authProvider;
|
||||
this._fetch = opts?.fetch;
|
||||
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
||||
this._sessionId = opts?.sessionId;
|
||||
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
||||
this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit;
|
||||
this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider;
|
||||
this._fetch = opts === null || opts === void 0 ? void 0 : opts.fetch;
|
||||
this._sessionId = opts === null || opts === void 0 ? void 0 : opts.sessionId;
|
||||
this._reconnectionOptions = (_a = opts === null || opts === void 0 ? void 0 : opts.reconnectionOptions) !== null && _a !== void 0 ? _a : DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
||||
}
|
||||
async _authThenStart() {
|
||||
var _a;
|
||||
if (!this._authProvider) {
|
||||
throw new UnauthorizedError('No auth provider');
|
||||
throw new UnauthorizedError("No auth provider");
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
scope: this._scope,
|
||||
fetchFn: this._fetchWithInit
|
||||
});
|
||||
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(error);
|
||||
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
||||
throw error;
|
||||
}
|
||||
if (result !== 'AUTHORIZED') {
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
return await this._startOrAuthSse({ resumptionToken: undefined });
|
||||
}
|
||||
async _commonHeaders() {
|
||||
var _a;
|
||||
const headers = {};
|
||||
if (this._authProvider) {
|
||||
const tokens = await this._authProvider.tokens();
|
||||
if (tokens) {
|
||||
headers['Authorization'] = `Bearer ${tokens.access_token}`;
|
||||
headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
||||
}
|
||||
}
|
||||
if (this._sessionId) {
|
||||
headers['mcp-session-id'] = this._sessionId;
|
||||
headers["mcp-session-id"] = this._sessionId;
|
||||
}
|
||||
if (this._protocolVersion) {
|
||||
headers['mcp-protocol-version'] = this._protocolVersion;
|
||||
headers["mcp-protocol-version"] = this._protocolVersion;
|
||||
}
|
||||
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
|
||||
const extraHeaders = this._normalizeHeaders((_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers);
|
||||
return new Headers({
|
||||
...headers,
|
||||
...extraHeaders
|
||||
...extraHeaders,
|
||||
});
|
||||
}
|
||||
async _startOrAuthSse(options) {
|
||||
var _a, _b, _c;
|
||||
const { resumptionToken } = options;
|
||||
try {
|
||||
// Try to open an initial SSE stream with GET to listen for server messages
|
||||
// This is optional according to the spec - server may not support it
|
||||
const headers = await this._commonHeaders();
|
||||
headers.set('Accept', 'text/event-stream');
|
||||
headers.set("Accept", "text/event-stream");
|
||||
// Include Last-Event-ID header for resumable streams if provided
|
||||
if (resumptionToken) {
|
||||
headers.set('last-event-id', resumptionToken);
|
||||
headers.set("last-event-id", resumptionToken);
|
||||
}
|
||||
const response = await (this._fetch ?? fetch)(this._url, {
|
||||
method: 'GET',
|
||||
const response = await ((_a = this._fetch) !== null && _a !== void 0 ? _a : fetch)(this._url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
signal: this._abortController?.signal
|
||||
signal: (_b = this._abortController) === null || _b === void 0 ? void 0 : _b.signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
await response.body?.cancel();
|
||||
if (response.status === 401 && this._authProvider) {
|
||||
// Need to authenticate
|
||||
return await this._authThenStart();
|
||||
@@ -107,7 +101,7 @@ export class StreamableHTTPClientTransport {
|
||||
this._handleSseStream(response.body, options, true);
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(error);
|
||||
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -118,38 +112,47 @@ export class StreamableHTTPClientTransport {
|
||||
* @returns Time to wait in milliseconds before next reconnection attempt
|
||||
*/
|
||||
_getNextReconnectionDelay(attempt) {
|
||||
// Use server-provided retry value if available
|
||||
if (this._serverRetryMs !== undefined) {
|
||||
return this._serverRetryMs;
|
||||
}
|
||||
// Fall back to exponential backoff
|
||||
// Access default values directly, ensuring they're never undefined
|
||||
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
|
||||
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
|
||||
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
|
||||
// Cap at maximum delay
|
||||
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
|
||||
}
|
||||
_normalizeHeaders(headers) {
|
||||
if (!headers)
|
||||
return {};
|
||||
if (headers instanceof Headers) {
|
||||
return Object.fromEntries(headers.entries());
|
||||
}
|
||||
if (Array.isArray(headers)) {
|
||||
return Object.fromEntries(headers);
|
||||
}
|
||||
return { ...headers };
|
||||
}
|
||||
/**
|
||||
* Schedule a reconnection attempt using server-provided retry interval or backoff
|
||||
* Schedule a reconnection attempt with exponential backoff
|
||||
*
|
||||
* @param lastEventId The ID of the last received event for resumability
|
||||
* @param attemptCount Current reconnection attempt count for this specific stream
|
||||
*/
|
||||
_scheduleReconnection(options, attemptCount = 0) {
|
||||
var _a;
|
||||
// Use provided options or default options
|
||||
const maxRetries = this._reconnectionOptions.maxRetries;
|
||||
// Check if we've exceeded maximum retry attempts
|
||||
if (attemptCount >= maxRetries) {
|
||||
this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
|
||||
if (maxRetries > 0 && attemptCount >= maxRetries) {
|
||||
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
|
||||
return;
|
||||
}
|
||||
// Calculate next delay based on current attempt count
|
||||
const delay = this._getNextReconnectionDelay(attemptCount);
|
||||
// Schedule the reconnection
|
||||
this._reconnectionTimeout = setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
// Use the last event ID to resume where we left off
|
||||
this._startOrAuthSse(options).catch(error => {
|
||||
this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
|
||||
var _a;
|
||||
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
|
||||
// Schedule another attempt if this one failed, incrementing the attempt counter
|
||||
this._scheduleReconnection(options, attemptCount + 1);
|
||||
});
|
||||
@@ -161,25 +164,15 @@ export class StreamableHTTPClientTransport {
|
||||
}
|
||||
const { onresumptiontoken, replayMessageId } = options;
|
||||
let lastEventId;
|
||||
// Track whether we've received a priming event (event with ID)
|
||||
// Per spec, server SHOULD send a priming event with ID before closing
|
||||
let hasPrimingEvent = false;
|
||||
// Track whether we've received a response - if so, no need to reconnect
|
||||
// Reconnection is for when server disconnects BEFORE sending response
|
||||
let receivedResponse = false;
|
||||
const processStream = async () => {
|
||||
var _a, _b, _c, _d;
|
||||
// this is the closest we can get to trying to catch network errors
|
||||
// if something happens reader will throw
|
||||
try {
|
||||
// Create a pipeline: binary stream -> text decoder -> SSE parser
|
||||
const reader = stream
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(new EventSourceParserStream({
|
||||
onRetry: (retryMs) => {
|
||||
// Capture server-provided retry value for reconnection timing
|
||||
this._serverRetryMs = retryMs;
|
||||
}
|
||||
}))
|
||||
.pipeThrough(new EventSourceParserStream())
|
||||
.getReader();
|
||||
while (true) {
|
||||
const { value: event, done } = await reader.read();
|
||||
@@ -189,54 +182,29 @@ export class StreamableHTTPClientTransport {
|
||||
// Update last event ID if provided
|
||||
if (event.id) {
|
||||
lastEventId = event.id;
|
||||
// Mark that we've received a priming event - stream is now resumable
|
||||
hasPrimingEvent = true;
|
||||
onresumptiontoken?.(event.id);
|
||||
onresumptiontoken === null || onresumptiontoken === void 0 ? void 0 : onresumptiontoken(event.id);
|
||||
}
|
||||
// Skip events with no data (priming events, keep-alives)
|
||||
if (!event.data) {
|
||||
continue;
|
||||
}
|
||||
if (!event.event || event.event === 'message') {
|
||||
if (!event.event || event.event === "message") {
|
||||
try {
|
||||
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
||||
if (isJSONRPCResultResponse(message)) {
|
||||
// Mark that we received a response - no need to reconnect for this request
|
||||
receivedResponse = true;
|
||||
if (replayMessageId !== undefined) {
|
||||
message.id = replayMessageId;
|
||||
}
|
||||
if (replayMessageId !== undefined && isJSONRPCResponse(message)) {
|
||||
message.id = replayMessageId;
|
||||
}
|
||||
this.onmessage?.(message);
|
||||
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(error);
|
||||
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle graceful server-side disconnect
|
||||
// Server may close connection after sending event ID and retry field
|
||||
// Reconnect if: already reconnectable (GET stream) OR received a priming event (POST stream with event ID)
|
||||
// BUT don't reconnect if we already received a response - the request is complete
|
||||
const canResume = isReconnectable || hasPrimingEvent;
|
||||
const needsReconnect = canResume && !receivedResponse;
|
||||
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
||||
this._scheduleReconnection({
|
||||
resumptionToken: lastEventId,
|
||||
onresumptiontoken,
|
||||
replayMessageId
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Handle stream errors - likely a network disconnect
|
||||
this.onerror?.(new Error(`SSE stream disconnected: ${error}`));
|
||||
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`));
|
||||
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
|
||||
// Reconnect if: already reconnectable (GET stream) OR received a priming event (POST stream with event ID)
|
||||
// BUT don't reconnect if we already received a response - the request is complete
|
||||
const canResume = isReconnectable || hasPrimingEvent;
|
||||
const needsReconnect = canResume && !receivedResponse;
|
||||
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
|
||||
if (isReconnectable &&
|
||||
this._abortController &&
|
||||
!this._abortController.signal.aborted) {
|
||||
// Use the exponential backoff reconnection strategy
|
||||
try {
|
||||
this._scheduleReconnection({
|
||||
@@ -246,7 +214,7 @@ export class StreamableHTTPClientTransport {
|
||||
}, 0);
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
||||
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +223,7 @@ export class StreamableHTTPClientTransport {
|
||||
}
|
||||
async start() {
|
||||
if (this._abortController) {
|
||||
throw new Error('StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.');
|
||||
throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
||||
}
|
||||
this._abortController = new AbortController();
|
||||
}
|
||||
@@ -264,153 +232,96 @@ export class StreamableHTTPClientTransport {
|
||||
*/
|
||||
async finishAuth(authorizationCode) {
|
||||
if (!this._authProvider) {
|
||||
throw new UnauthorizedError('No auth provider');
|
||||
throw new UnauthorizedError("No auth provider");
|
||||
}
|
||||
const result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
authorizationCode,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
scope: this._scope,
|
||||
fetchFn: this._fetchWithInit
|
||||
});
|
||||
if (result !== 'AUTHORIZED') {
|
||||
throw new UnauthorizedError('Failed to authorize');
|
||||
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError("Failed to authorize");
|
||||
}
|
||||
}
|
||||
async close() {
|
||||
if (this._reconnectionTimeout) {
|
||||
clearTimeout(this._reconnectionTimeout);
|
||||
this._reconnectionTimeout = undefined;
|
||||
}
|
||||
this._abortController?.abort();
|
||||
this.onclose?.();
|
||||
var _a, _b;
|
||||
// Abort any pending requests
|
||||
(_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort();
|
||||
(_b = this.onclose) === null || _b === void 0 ? void 0 : _b.call(this);
|
||||
}
|
||||
async send(message, options) {
|
||||
var _a, _b, _c, _d;
|
||||
try {
|
||||
const { resumptionToken, onresumptiontoken } = options || {};
|
||||
if (resumptionToken) {
|
||||
// If we have at last event ID, we need to reconnect the SSE stream
|
||||
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch(err => this.onerror?.(err));
|
||||
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
|
||||
return;
|
||||
}
|
||||
const headers = await this._commonHeaders();
|
||||
headers.set('content-type', 'application/json');
|
||||
headers.set('accept', 'application/json, text/event-stream');
|
||||
headers.set("content-type", "application/json");
|
||||
headers.set("accept", "application/json, text/event-stream");
|
||||
const init = {
|
||||
...this._requestInit,
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(message),
|
||||
signal: this._abortController?.signal
|
||||
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
|
||||
};
|
||||
const response = await (this._fetch ?? fetch)(this._url, init);
|
||||
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
|
||||
// Handle session ID received during initialization
|
||||
const sessionId = response.headers.get('mcp-session-id');
|
||||
const sessionId = response.headers.get("mcp-session-id");
|
||||
if (sessionId) {
|
||||
this._sessionId = sessionId;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => null);
|
||||
if (response.status === 401 && this._authProvider) {
|
||||
// Prevent infinite recursion when server returns 401 after successful auth
|
||||
if (this._hasCompletedAuthFlow) {
|
||||
throw new StreamableHTTPError(401, 'Server returned 401 after successful authentication');
|
||||
}
|
||||
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
||||
this._resourceMetadataUrl = resourceMetadataUrl;
|
||||
this._scope = scope;
|
||||
const result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
scope: this._scope,
|
||||
fetchFn: this._fetchWithInit
|
||||
});
|
||||
if (result !== 'AUTHORIZED') {
|
||||
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
|
||||
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
|
||||
if (result !== "AUTHORIZED") {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
// Mark that we completed auth flow
|
||||
this._hasCompletedAuthFlow = true;
|
||||
// Purposely _not_ awaited, so we don't call onerror twice
|
||||
return this.send(message);
|
||||
}
|
||||
if (response.status === 403 && this._authProvider) {
|
||||
const { resourceMetadataUrl, scope, error } = extractWWWAuthenticateParams(response);
|
||||
if (error === 'insufficient_scope') {
|
||||
const wwwAuthHeader = response.headers.get('WWW-Authenticate');
|
||||
// Check if we've already tried upscoping with this header to prevent infinite loops.
|
||||
if (this._lastUpscopingHeader === wwwAuthHeader) {
|
||||
throw new StreamableHTTPError(403, 'Server returned 403 after trying upscoping');
|
||||
}
|
||||
if (scope) {
|
||||
this._scope = scope;
|
||||
}
|
||||
if (resourceMetadataUrl) {
|
||||
this._resourceMetadataUrl = resourceMetadataUrl;
|
||||
}
|
||||
// Mark that upscoping was tried.
|
||||
this._lastUpscopingHeader = wwwAuthHeader ?? undefined;
|
||||
const result = await auth(this._authProvider, {
|
||||
serverUrl: this._url,
|
||||
resourceMetadataUrl: this._resourceMetadataUrl,
|
||||
scope: this._scope,
|
||||
fetchFn: this._fetch
|
||||
});
|
||||
if (result !== 'AUTHORIZED') {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
return this.send(message);
|
||||
}
|
||||
}
|
||||
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
||||
const text = await response.text().catch(() => null);
|
||||
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
|
||||
}
|
||||
// Reset auth loop flag on successful response
|
||||
this._hasCompletedAuthFlow = false;
|
||||
this._lastUpscopingHeader = undefined;
|
||||
// If the response is 202 Accepted, there's no body to process
|
||||
if (response.status === 202) {
|
||||
await response.body?.cancel();
|
||||
// if the accepted notification is initialized, we start the SSE stream
|
||||
// if it's supported by the server
|
||||
if (isInitializedNotification(message)) {
|
||||
// Start without a lastEventId since this is a fresh connection
|
||||
this._startOrAuthSse({ resumptionToken: undefined }).catch(err => this.onerror?.(err));
|
||||
this._startOrAuthSse({ resumptionToken: undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Get original message(s) for detecting request IDs
|
||||
const messages = Array.isArray(message) ? message : [message];
|
||||
const hasRequests = messages.filter(msg => 'method' in msg && 'id' in msg && msg.id !== undefined).length > 0;
|
||||
const hasRequests = messages.filter(msg => "method" in msg && "id" in msg && msg.id !== undefined).length > 0;
|
||||
// Check the response type
|
||||
const contentType = response.headers.get('content-type');
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (hasRequests) {
|
||||
if (contentType?.includes('text/event-stream')) {
|
||||
if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("text/event-stream")) {
|
||||
// Handle SSE stream responses for requests
|
||||
// We use the same handler as standalone streams, which now supports
|
||||
// reconnection with the last event ID
|
||||
this._handleSseStream(response.body, { onresumptiontoken }, false);
|
||||
}
|
||||
else if (contentType?.includes('application/json')) {
|
||||
else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) {
|
||||
// For non-streaming servers, we might get direct JSON responses
|
||||
const data = await response.json();
|
||||
const responseMessages = Array.isArray(data)
|
||||
? data.map(msg => JSONRPCMessageSchema.parse(msg))
|
||||
: [JSONRPCMessageSchema.parse(data)];
|
||||
for (const msg of responseMessages) {
|
||||
this.onmessage?.(msg);
|
||||
(_c = this.onmessage) === null || _c === void 0 ? void 0 : _c.call(this, msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await response.body?.cancel();
|
||||
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No requests in message but got 200 OK - still need to release connection
|
||||
await response.body?.cancel();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(error);
|
||||
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -429,6 +340,7 @@ export class StreamableHTTPClientTransport {
|
||||
* the server does not allow clients to terminate sessions.
|
||||
*/
|
||||
async terminateSession() {
|
||||
var _a, _b, _c;
|
||||
if (!this._sessionId) {
|
||||
return; // No session to terminate
|
||||
}
|
||||
@@ -436,12 +348,11 @@ export class StreamableHTTPClientTransport {
|
||||
const headers = await this._commonHeaders();
|
||||
const init = {
|
||||
...this._requestInit,
|
||||
method: 'DELETE',
|
||||
method: "DELETE",
|
||||
headers,
|
||||
signal: this._abortController?.signal
|
||||
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
|
||||
};
|
||||
const response = await (this._fetch ?? fetch)(this._url, init);
|
||||
await response.body?.cancel();
|
||||
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
|
||||
// We specifically handle 405 as a valid response according to the spec,
|
||||
// meaning the server does not support explicit session termination
|
||||
if (!response.ok && response.status !== 405) {
|
||||
@@ -450,7 +361,7 @@ export class StreamableHTTPClientTransport {
|
||||
this._sessionId = undefined;
|
||||
}
|
||||
catch (error) {
|
||||
this.onerror?.(error);
|
||||
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -460,18 +371,5 @@ export class StreamableHTTPClientTransport {
|
||||
get protocolVersion() {
|
||||
return this._protocolVersion;
|
||||
}
|
||||
/**
|
||||
* Resume an SSE stream from a previous event ID.
|
||||
* Opens a GET SSE connection with Last-Event-ID header to replay missed events.
|
||||
*
|
||||
* @param lastEventId The event ID to resume from
|
||||
* @param options Optional callback to receive new resumption tokens
|
||||
*/
|
||||
async resumeStream(lastEventId, options) {
|
||||
await this._startOrAuthSse({
|
||||
resumptionToken: lastEventId,
|
||||
onresumptiontoken: options?.onresumptiontoken
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=streamableHttp.js.map
|
||||
Reference in New Issue
Block a user