مدیریت جلسات برای Live API

Gemini Live API جاری‌سازی‌های پیوسته صدا یا نوشتار را که جلسه نامیده می‌شود پردازش می‌کند. می‌توانید چرخه حیات جلسه را از دست دادن اولیه تا پایان خوش مدیریت کنید.

محدودیت‌های جلسه‌ها

برای Live API، جلسه به اتصالی پایدار اشاره دارد که در آن ورودی و برونداد به‌طور مداوم ازطریق اتصال جاری‌سازی می‌شوند.

اگر جلسه از هریک از محدودیت‌های زیر فراتر رود، اتصال قطع می‌شود. بااین‌حال، توجه داشته باشید که Live API گزینه‌هایی (در زیر ببینید) برای مدیریت این محدودیت‌های مربوط به جلسه ارائه می‌دهد.

  • پنجره زمینه‌ای جلسه به ۱۲۸ هزار داده‌واحد محدود است.

    به‌دلیل این محدودیت پنجره بافت، در اینجا حداکثر طول تقریبی جلسه براساس روش‌های ورودی آورده شده است:

    • جلسات ورودی فقط صوتی به ۱۵ دقیقه محدود است.
    • ورودی ویدیو + صدا به ۲ دقیقه محدود می‌شود.
  • طول اتصال به حدود ۱۰ دقیقه محدود است.

    حدود ۶۰ ثانیه قبل‌از پایان اتصال، اعلان رفتن دریافت خواهید کرد.

در اینجا چند گزینه برای مدیریت محدودیت‌های مربوط به جلسه ارائه شده است:

شروع جلسه

برای دیدن گزیده کامل نحوه شروع جلسه، به راهنمای شروع کار با Live API مراجعه کنید.

به‌روزرسانی در میان جلسه

مدل‌های Live API از قابلیت‌های پیشرفته زیر برای به‌روزرسانی‌های میان جلسه پشتیبانی می‌کنند:

افزودن به‌روزرسانی‌های محتوای افزایشی

می‌توانید درطول جلسه فعال، به‌روزرسانی‌های افزایشی اضافه کنید. از این برای ارسال ورودی نوشتاری، ایجاد بافت جلسه، یا بازیابی بافت جلسه استفاده کنید.

  • برای بافت‌های طولانی‌تر، توصیه می‌کنیم خلاصه‌ای از پیام واحدی ارائه دهید تا پنجره بافت برای تعاملات بعدی آزاد شود.

  • برای زمینه‌های کوتاه، می‌توانید تعاملات نوبتی ارسال کنید تا نشان‌دهنده توالی دقیق رویدادها باشد، مانند گلچین زیر.

Swift

// Define initial turns (history/context).
let turns: [ModelContent] = [
  ModelContent(role: "user", parts: [TextPart("What is the capital of France?")]),
  ModelContent(role: "model", parts: [TextPart("Paris")]),
]

// Send history, keeping the conversational turn OPEN (false).
await session.sendContent(turns, turnComplete: false)

// Define the new user query.
let newTurn: [ModelContent] = [
  ModelContent(role: "user", parts: [TextPart("What is the capital of Germany?")]),
]

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.sendContent(newTurn, turnComplete: true)

Kotlin

// Define initial turns (history/context).
val turns = listOf(
  content("user") {
    text("What is the capital of France?")
  },
  content("model") {
    text("Paris")
  }
)

// Send history, keeping the conversational turn OPEN (false).
turns.forEach {
  session.send(
    content = it,
    turnComplete = false
  )
}

// Define the new user query.
val newTurn = content("user") {
  text("What is the capital of Germany?")
}

// Send the final query, CLOSING the turn (true) to trigger the model response.
session.send(
  content = newTurn,
  turnComplete = true
)

Java

// Define initial turns (history/context).
List turns =
    Arrays.asList(
        new Content.Builder().setRole("user").addText("What is the capital of France?").build(),
        new Content.Builder().setRole("model").addText("Paris").build());

for (Content turn : turns) {
  session.send(
    turn,
    false // turnComplete: false
  );
}

// Define the new user query.
Content newTurn = new Content.Builder().addText("What is the capital of Germany?").build();

// Send the final query, CLOSING the turn (true) to trigger the model response.
session.send(
  newTurn,
  true // isTurnComplete: true
);

Web

const turns = [{ text: "Hello from the user!" }];

await session.send(
  turns,
  false // turnComplete: false
);

console.log("Sent history. Waiting for next input...");

// Define the new user query.
const newTurn [{ text: "And what is the capital of Germany?" }];

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.send(
    newTurn,
    true // turnComplete: true
);
console.log("Sent final query. Model response expected now.");

Dart

// Define initial turns (history/context).
final List turns = [
  Content(
    "user",
    [Part.text("What is the capital of France?")],
  ),
  Content(
    "model",
    [Part.text("Paris")],
  ),
];

// Send history, keeping the conversational turn OPEN (false).
await session.send(
  input: turns,
  turnComplete: false,
);

// Define the new user query.
final List newTurn = [
  Content(
    "user",
    [Part.text("What is the capital of Germany?")],
  ),
];

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.send(
  input: newTurn,
  turnComplete: true,
);

Unity

// Define initial turns (history/context).
List turns = new List {
    new ModelContent("user", new ModelContent.TextPart("What is the capital of France?") ),
    new ModelContent("model", new ModelContent.TextPart("Paris") ),
};

// Send history, keeping the conversational turn OPEN (false).
foreach (ModelContent turn in turns)
{
    await session.SendAsync(
        content: turn,
        turnComplete: false
    );
}

// Define the new user query.
ModelContent newTurn = ModelContent.Text("What is the capital of Germany?");

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.SendAsync(
    content: newTurn,
    turnComplete: true
);

به‌روزرسانی دستورالعمل‌های سیستم در میان جلسه

فقط هنگام استفاده از Agent Platform Gemini API (formerly Vertex AI) به‌عنوان ارائه‌دهنده میانای برنامه‌سازی کاربردی دردسترس است.

می‌توانید دستورالعمل‌های سیستم را درطول جلسه فعال به‌روز کنید. از این برای تطبیق دادن پاسخ‌های مدل استفاده کنید، برای مثال برای تغییر زبان پاسخ یا اصلاح لحن.

برای به‌روزرسانی دستورالعمل‌های سیستم در حین جلسه، می‌توانید محتوای نوشتاری با نقش system ارسال کنید. دستورالعمل‌های سیستم به‌روزشده برای بقیه جلسه اعمال خواهد شد.

Swift

await session.sendContent(
  [ModelContent(
    role: "system",
    parts: [TextPart("new system instruction")]
  )],
  turnComplete: false
)

Kotlin

// In a coroutine scope
session.send(
    content = content("system") {
        text("new system instruction")
    },
    turnComplete = false
)

Java

session.send(
    new Content.Builder()
        .setRole("system")
        .addText("new system instruction")
        .build(),
    /* turnComplete: */ false
);

Web

Not yet supported for Web apps - check back soon!

Dart

try {
  await _session.send(
    input: Content(
      'system',
      [Part.text('new system instruction')],
    ),
    turnComplete: false,
  );
} catch (e) {
  print('Failed to update system instructions: $e');
}

Unity

try
{
    await session.SendAsync(
        content: new ModelContent(
            "system",
            new ModelContent.TextPart("new system instruction")
        ),
        turnComplete: false
    );
}
catch (Exception e)
{
    Debug.LogError($"Failed to update system instructions: {e.Message}");
}

فشرده کردن پنجره بافت

روی ارائه‌دهنده Gemini API خود کلیک کنید تا محتوا و کد مخصوص ارائه‌دهنده را در این صفحه مشاهده کنید.

Live API پنجره زمینه‌ای جلسه داده‌های جاری‌سازی‌شده هم‌زمان (۲۵ کد در ثانیه (TPS) برای صدا و ۲۵۸ کد در ثانیه برای ویدیو) و همچنین محتوای دیگر، ازجمله ورودی‌های نوشتاری و بروندادهای مدل را ذخیره می‌کند. همه مدل‌های Live API دارای محدودیت پنجره زمینه‌ای جلسه ۱۲۸ هزار داده‌واحد (توکن) هستند.

به‌طور پیش‌فرض، به‌دلیل این محدودیت پنجره بافت، در اینجا حداکثر طول تقریبی جلسه براساس روش‌های ورودی آورده شده است:

  • جلسات ورودی فقط صوتی به ۱۵ دقیقه محدود است.
  • ورودی ویدیو + صدا به ۲ دقیقه محدود می‌شود.

در جلسات طولانی، با پیشرفت مکالمه، سابقه نشان‌های صوتی و/یا ویدیویی جمع‌آوری می‌شود. اگر این سابقه از حد مدل فراتر رود، ممکن است مدل توهم بزند، کند شود، یا جلسه به‌اجبار بسته شود.

برای فعال کردن جلسه‌های طولانی‌تر، می‌توانید فشرده‌سازی پنجره بافت را با تنظیم کردن فیلد contextWindowCompression به‌عنوان بخشی از LiveGenerationConfig فعال کنید. وقتی فعال باشد، سرور از سازوکار پنجره لغزنده برای دور انداختن خودکار قدیمی‌ترین نوبت‌ها یا خلاصه کردن آن‌ها برای حفظ اندازه زمینه‌ای در محدوده پیش‌فرض یا مشخص‌شده استفاده می‌کند. دستورالعمل‌های سیستم دور انداخته نمی‌شوند و همیشه در ابتدای پنجره زمینه‌ای باقی می‌مانند.

از دیدگاه کاربر، این امر امکان می‌دهد که مدت زمان جلسه به‌طور نظری بی‌نهایت باشد، زیرا «حافظه» به‌طور مداوم مدیریت می‌شود.

می‌توانید سازوکار پنجره لغزنده و همچنین اختیاراً تعداد نشانه‌هایی را که فشرده‌سازی را راه‌اندازی می‌کند پیکربندی کنید (تنظیمات و مقادیر دردسترس را در زیر ببینید). در اینجا چند ملاحظه سطح بالا درباره استفاده از این تنظیمات آورده شده است:

  • تنظیم targetTokens روی مقدار بسیار پایین باعث می‌شود فضای اتاق زمینه‌ای بیشتری برای جاری‌سازی‌های پیوسته آزاد شود، اما مدل به‌سرعت نوبت‌های قدیمی‌تر مکالمه را «فراموش» می‌کند.

  • تنظیم targetTokens نزدیک‌تر به triggerTokens حافظه بیشتری را حفظ می‌کند اما روال‌های فشرده‌سازی را بسیار بیشتر راه‌اندازی می‌کند.

تنظیم اگر در پیکربندی تنظیم نشده باشد، پیش‌فرض برای پنجره لغزنده است حداقل مقدار حداکثر مقدار
triggerTokens
طول زمینه‌ای قبل‌از راه‌اندازی فشرده‌سازی
‫٪۸۰ از حد مجاز پنجره بافت مدل ۵٬۰۰۰ ۱۲۸٬۰۰۰
targetTokens
تعداد هدف نشانه‌ها برای نگهداری
‫٪۵۰ مقدار triggerTokens
  • اگر triggerTokens به‌صورت صریح تنظیم نشده باشد، targetTokens به‌طور پیش‌فرض روی ۵۰٪ از مقدار پیش‌فرض triggerTokens تنظیم می‌شود.
  • مقدار targetTokens باید کمتر از مقدار triggerTokens باشد.
0 ۱۲۸٬۰۰۰

Swift


// ...

let liveModel = FirebaseAI.firebaseAI(backend: .googleAI()).liveModel(
  modelName: "GEMINI_LIVE_API_MODEL_NAME",
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  generationConfig: LiveGenerationConfig(
    responseModalities: [.audio],
    contextWindowCompression: ContextWindowCompressionConfig(
      triggerTokens: 10000,
      slidingWindow: SlidingWindow(
        targetTokens: 2000,
      )
    )
  )
)

Kotlin


// ...

val liveModel = Firebase.ai(backend = GenerativeBackend.googleAI()).liveModel(
    modelName = "GEMINI_LIVE_API_MODEL_NAME",
    // Enable context window compression.
    // (Optional) Configure the number of tokens in the context window that triggers the compression.
    generationConfig = liveGenerationConfig {
        responseModality = ResponseModality.AUDIO,
        contextWindowCompression = ContextWindowCompressionConfig(
            triggerTokens = 10000,
            slidingWindow = SlidingWindow(targetTokens = 2000)
        )
    }
)

Java


// ...

LiveGenerativeModel lm = FirebaseAI.getInstance(GenerativeBackend.googleAI()).liveModel(
        "GEMINI_LIVE_API_MODEL_NAME",
        // Enable context window compression.
        // (Optional) Configure the number of tokens in the context window that triggers the compression.
        new LiveGenerationConfig.Builder()
                .setResponseModality(ResponseModality.AUDIO)
                .setContextWindowCompression(
                        new ContextWindowCompressionConfig(10000, new SlidingWindow(2000))
                )
                .build()
);

Web


// ...

const ai = getAI(firebaseApp, { backend: new GoogleAIBackend() });

const liveModel = getLiveGenerativeModel(ai, {
  model: "GEMINI_LIVE_API_MODEL_NAME",
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  generationConfig: {
    responseModalities: [ResponseModality.AUDIO],
    contextWindowCompression: {
      triggerTokens: 10000,
      slidingWindow: {
        targetTokens: 2000,
      },
    },
  },
});

Dart


// ...

final _liveModel = FirebaseAI.googleAI().liveGenerativeModel(
  model: 'GEMINI_LIVE_API_MODEL_NAME',
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  liveGenerationConfig: LiveGenerationConfig(
    responseModalities: [ResponseModalities.audio],
    contextWindowCompression: ContextWindowCompressionConfig(
      triggerTokens: 10000,
      slidingWindow: SlidingWindow(targetTokens: 2000),
    ),
  ),
);

Unity


// ...

var liveModel = FirebaseAI.GetInstance(FirebaseAI.Backend.GoogleAI()).GetLiveModel(
    modelName: "GEMINI_LIVE_API_MODEL_NAME",
    // Enable context window compression.
    // (Optional) Configure the number of tokens in the context window that triggers the compression.
    liveGenerationConfig: new LiveGenerationConfig(
        responseModalities: new[] { ResponseModality.Audio },
        contextWindowCompression: new ContextWindowCompressionConfig(
            triggerTokens: 10000,
            slidingWindow: new SlidingWindow(targetTokens: 2000)
        )
    )
);

تشخیص اینکه جلسه چه زمانی پایان می‌یابد

حداکثر مدت یک اتصال WebSocket پیوسته حدوداً ۱۰ دقیقه است. اعلان رفتن ‏۶۰ ثانیه قبل‌از پایان اتصال به مشتری ارسال می‌شود که می‌تواند به شما کمک کند اقدامات بیشتری انجام دهید (برای مثال، با ازسر گرفتن جلسه).

مثال زیر نشان می‌دهد که چگونه با گوش دادن به اعلان going away، پایان قریب‌الوقوع اتصال را تشخیص دهید:

Swift

for try await response in session.responses {
  switch response.payload {

  case .goingAwayNotice(let goingAwayNotice):
    // Prepare for the session to close soon
    if let timeLeft = goingAwayNotice.timeLeft {
        print("Server going away in \(timeLeft) seconds")
    }
  }
}

Kotlin

for (response in session.responses) {
    when (val message = response.payload) {
        is LiveServerGoAway -> {
            // Prepare for the session to close soon
            val remaining = message.timeLeft
            logger.info("Server going away in $remaining")
        }
    }
}

Java

session.getResponses().forEach(response -> {
    if (response.getPayload() instanceof LiveServerResponse.GoingAwayNotice) {
        LiveServerResponse.GoingAwayNotice notice = (LiveServerResponse.GoingAwayNotice) response.getPayload();
        // Prepare for the session to close soon
        Duration timeLeft = notice.getTimeLeft();
    }
});

Web

for await (const message of session.receive()) {
  switch (message.type) {

  ...
  case "goingAwayNotice":
    console.log("Server going away. Time left:", message.timeLeft);
    break;
  }
}

Dart

Future _handleLiveServerMessage(LiveServerResponse response) async {
  final message = response.message;
  if (message is GoingAwayNotice) {
     // Prepare for the session to close soon
     developer.log('Server going away. Time left: ${message.timeLeft}');
  }
}

Unity

foreach (var response in session.Responses) {
    if (response.Payload is LiveSessionGoingAway notice) {
        // Prepare for the session to close soon
        TimeSpan timeLeft = notice.TimeLeft;
        Debug.Log($"Server going away notice received. Remaining: {timeLeft}");
    }
}

ازسرگیری جلسه

‫Live API از ازسرگیری جلسه پشتیبانی می‌کند تا بافت مکالمه ازدست نرود. هر جلسه یک نام کاربری دارد و می‌توان از آن به روش‌های زیر استفاده کرد:

  • حفظ جلسه قبل‌از رسیدن به محدودیت زمانی اتصال

    حداکثر مدت یک اتصال WebSocket پیوسته حدوداً ۱۰ دقیقه است. می‌توانید با گوش دادن به اعلان رفتن، و سپس با ایجاد اتصال جدید بااستفاده از دستگیره جلسه، جلسه را تمدید کنید.

  • ازسرگیری جلسه درست پس‌از قطع شدن اتصال

    اگر اتصال قبل‌از رسیدن به حداکثر زمان مجاز اتصال قطع شود یا ازدست برود (برای مثال، تغییر از Wi-Fi به 5G)، سرور وضعیت جلسه را برای حدود ۱۰ دقیقه حفظ می‌کند. در این بازه زمانی، می‌توانید با ایجاد اتصال جدید بااستفاده از دستگیره جلسه، جلسه را ازسر بگیرید.

  • ازسرگیری جلسه پس‌از یک دوره زمانی طولانی

    پس‌از پایان اتصال، سرور وضعیت جلسه را برای چند ساعت حفظ می‌کند. در این بازه زمانی، می‌توانید با ایجاد اتصال جدید بااستفاده از دستگیره جلسه، جلسه را ازسر بگیرید. توجه داشته باشید که این بازه زمانی برای دو ارائه‌دهنده Gemini API متفاوت است: Gemini Developer API ۲ ساعت | Agent Platform Gemini API (formerly Vertex AI) ۲۴ ساعت.

به‌طور پیش‌فرض، ازسرگیری جلسه غیرفعال است. برای فعال کردن ازسرگیری جلسه، هنگام ایجاد اتصال جدید، پیکربندی ازسرگیری خالی را ارسال کنید. وقتی فعال باشد، سرور به‌طور دوره‌ای به‌روزرسانی‌هایی حاوی دستگیره ازسرگیری جلسه ارسال می‌کند. اگر اتصال جلسه قطع شد، می‌توانید دوباره متصل شوید و این دستگیره را برای ازسر گرفتن جلسه با بافت دست‌نخورده آن ارسال کنید.

مثال‌های زیر دو گزینه برای ازسرگیری جلسه را نشان می‌دهد:

Swift

// Local variable to save the active session handle
var activeSessionHandle: String?

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = try await liveModel.connect(
  sessionResumption: SessionResumptionConfig()
)

// Start receiving responses
for try await message in session.responses {
  // Check for new session handles inside your message handling loop
  switch message.payload {
  case let .sessionResumptionUpdate(updateMessage):
    guard let newHandle = updateMessage.newHandle, updateMessage.resumable else {
      continue
    }
    activeSessionHandle = newHandle
    print("SessionResumptionUpdate: handle \(newHandle)")
  // ... handle other LiveServerMessage types ...
  default:
    break
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if let handle = activeSessionHandle {
  session = try await liveModel.connect(
    sessionResumption: SessionResumptionConfig(handle: handle)
  )
}

// Option 2: Resume the session directly on an existing session object
if let handle = activeSessionHandle {
  try await session.resumeSession(
    sessionResumption: SessionResumptionConfig(handle: handle)
  )
}

Kotlin

// Local variable to save the active session handle
var activeSessionHandle: String? = null

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = liveModel.connect(
    sessionResumption = SessionResumptionConfig()
)

// Start receiving responses
session.receive().collect { message ->
    // Process other received response types...

    // Check for new session handles inside your message handling loop
    if (message is LiveSessionResumptionUpdate) {
        if (message.resumable == true && message.newHandle != null) {
            activeSessionHandle = message.newHandle
            Log.d("TAG", "SessionResumptionUpdate: handle ${message.newHandle}")
        }
    }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
activeSessionHandle?.let { handle ->
    session = liveModel.connect(
        sessionResumption = SessionResumptionConfig(handle = handle)
    )
}

// Option 2: Resume the session directly on an existing session object
activeSessionHandle?.let { handle ->
    session.resumeSession(
        sessionResumption = SessionResumptionConfig(handle = handle)
    )
}

Java

For Java, session resumption is not yet supported. Check back soon!

Web

// Local variable to save the active session handle
let activeSessionHandle = null;

// Initialize the session. Passing an empty object requests the server to send SessionResumptionUpdate
let session = await liveModel.connect({});

// Start receiving responses
for await (const message of session.receive()) {
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  if (message.type === 'sessionResumptionUpdate') {
    if (message.resumable && message.newHandle) {
      activeSessionHandle = message.newHandle;
      console.log(`SessionResumptionUpdate: handle ${activeSessionHandle}`);
    }
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (activeSessionHandle) {
  session = await liveModel.connect({
    handle: activeSessionHandle
  });
}

// Option 2: Resume the session directly on an existing session object
if (activeSessionHandle) {
  await session.resumeSession({
    handle: activeSessionHandle
  });
}

Dart

// Local variable to save the active session handle
String? _activeSessionHandle;

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var _session = await _liveModel.connect(
  sessionResumption: SessionResumptionConfig(),
);

// Start receiving responses
await for (final message in _session.receive()) {
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  if (message is SessionResumptionUpdate &&
      message.resumable != null &&
      message.resumable!) {
    _activeSessionHandle = message.newHandle;
    log('SessionResumptionUpdate: handle ${message.newHandle}');
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (_activeSessionHandle != null) {
  _session = await _liveModel.connect(
    sessionResumption: SessionResumptionConfig.resume(_activeSessionHandle!),
  );
}

// Option 2: Alternatively, resume the session directly on an existing session object
if (_activeSessionHandle != null) {
  await _session.resumeSession(
    sessionResumption: SessionResumptionConfig.resume(_activeSessionHandle!),
  );
}

Unity

// Local variable to save the active session handle
string activeSessionHandle = null;

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = await liveModel.ConnectAsync(
    sessionResumption: new SessionResumptionConfig()
);

// Start receiving responses
await foreach (var response in session.ReceiveAsync())
{
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  if (response.Message is LiveSessionResumptionUpdate updateMessage)
  {
    if (updateMessage.Resumable == true && !string.IsNullOrEmpty(updateMessage.NewHandle))
    {
      activeSessionHandle = updateMessage.NewHandle;
      Debug.Log($"SessionResumptionUpdate: handle {activeSessionHandle}");
    }
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (!string.IsNullOrEmpty(activeSessionHandle)) {
  session = await liveModel.ConnectAsync(
      sessionResumption: new SessionResumptionConfig(activeSessionHandle)
  );
}

// Option 2: Resume the session directly on an existing session object
if (!string.IsNullOrEmpty(activeSessionHandle)) {
  await session.ResumeSessionAsync(
      sessionResumption: new SessionResumptionConfig(activeSessionHandle)
  );
}