opencode-tbot 0.1.8 → 0.1.10

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.
package/dist/plugin.js CHANGED
@@ -255,58 +255,96 @@ var OpenCodeClient = class {
255
255
  this.fetchFn = fetchFn;
256
256
  }
257
257
  async getHealth() {
258
+ const rawClient = getRawSdkClient(this.client);
259
+ if (rawClient?.get) return await this.requestRaw("get", { url: "/global/health" });
258
260
  const healthEndpoint = this.client.global?.health;
259
261
  if (typeof healthEndpoint === "function") return unwrapSdkData(await healthEndpoint.call(this.client.global, SDK_OPTIONS));
260
- const rawClient = getRawSdkClient(this.client);
261
- if (!rawClient) throw new Error("OpenCode SDK client does not expose a compatible health endpoint.");
262
- return unwrapSdkData(await rawClient.get({
263
- url: "/global/health",
264
- ...SDK_OPTIONS
265
- }));
262
+ if (!rawClient?.get) throw new Error("OpenCode SDK client does not expose a compatible health endpoint.");
263
+ return this.requestRaw("get", { url: "/global/health" });
266
264
  }
267
265
  async abortSession(sessionId) {
266
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
267
+ url: "/session/{sessionID}/abort",
268
+ path: { sessionID: sessionId }
269
+ });
268
270
  return unwrapSdkData(await this.client.session.abort({ sessionID: sessionId }, SDK_OPTIONS));
269
271
  }
270
272
  async getPath() {
273
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/path" });
271
274
  return unwrapSdkData(await this.client.path.get(void 0, SDK_OPTIONS));
272
275
  }
273
276
  async listLspStatuses(directory) {
277
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
278
+ url: "/lsp",
279
+ ...directory ? { query: { directory } } : {}
280
+ });
274
281
  return unwrapSdkData(await this.client.lsp.status(directory ? { directory } : void 0, SDK_OPTIONS));
275
282
  }
276
283
  async listMcpStatuses(directory) {
284
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
285
+ url: "/mcp",
286
+ ...directory ? { query: { directory } } : {}
287
+ });
277
288
  return unwrapSdkData(await this.client.mcp.status(directory ? { directory } : void 0, SDK_OPTIONS));
278
289
  }
279
290
  async getSessionStatuses() {
291
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session/status" });
280
292
  return unwrapSdkData(await this.client.session.status(void 0, SDK_OPTIONS));
281
293
  }
282
294
  async listProjects() {
295
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project" });
283
296
  return unwrapSdkData(await this.client.project.list(void 0, SDK_OPTIONS));
284
297
  }
285
298
  async listSessions() {
299
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session" });
286
300
  return unwrapSdkData(await this.client.session.list(void 0, SDK_OPTIONS));
287
301
  }
288
302
  async getCurrentProject() {
303
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project/current" });
289
304
  return unwrapSdkData(await this.client.project.current(void 0, SDK_OPTIONS));
290
305
  }
291
306
  async createSessionForDirectory(directory, title) {
307
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
308
+ url: "/session",
309
+ query: { directory },
310
+ ...title ? { body: { title } } : {}
311
+ });
292
312
  return unwrapSdkData(await this.client.session.create(title ? {
293
313
  directory,
294
314
  title
295
315
  } : { directory }, SDK_OPTIONS));
296
316
  }
297
317
  async renameSession(sessionId, title) {
318
+ if (hasRawSdkMethod(this.client, "patch")) return this.requestRaw("patch", {
319
+ url: "/session/{sessionID}",
320
+ path: { sessionID: sessionId },
321
+ body: { title }
322
+ });
298
323
  return unwrapSdkData(await this.client.session.update({
299
324
  sessionID: sessionId,
300
325
  title
301
326
  }, SDK_OPTIONS));
302
327
  }
303
328
  async listAgents() {
329
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/agent" });
304
330
  return unwrapSdkData(await this.client.app.agents(void 0, SDK_OPTIONS));
305
331
  }
306
332
  async listPendingPermissions(directory) {
333
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
334
+ url: "/permission",
335
+ ...directory ? { query: { directory } } : {}
336
+ });
307
337
  return unwrapSdkData(await this.client.permission.list(directory ? { directory } : void 0, SDK_OPTIONS));
308
338
  }
309
339
  async replyToPermission(requestId, reply, message) {
340
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
341
+ url: "/permission/{requestID}/reply",
342
+ path: { requestID: requestId },
343
+ body: {
344
+ reply,
345
+ ...message?.trim() ? { message: message.trim() } : {}
346
+ }
347
+ });
310
348
  return unwrapSdkData(await this.client.permission.reply({
311
349
  requestID: requestId,
312
350
  reply,
@@ -337,14 +375,7 @@ var OpenCodeClient = class {
337
375
  }))];
338
376
  if (parts.length === 0) throw new Error("Prompt requires text or file attachments.");
339
377
  const knownMessageIds = await this.captureKnownMessageIds(input.sessionId);
340
- const initialData = unwrapSdkData(await this.client.session.prompt({
341
- sessionID: input.sessionId,
342
- ...input.agent ? { agent: input.agent } : {},
343
- ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
344
- ...input.model ? { model: input.model } : {},
345
- ...input.variant ? { variant: input.variant } : {},
346
- parts
347
- }, SDK_OPTIONS));
378
+ const initialData = await this.sendPromptRequest(input, parts);
348
379
  return buildPromptSessionResult(await this.resolvePromptResponse(input, initialData, knownMessageIds), {
349
380
  emptyResponseText: EMPTY_RESPONSE_TEXT,
350
381
  finishedAt: Date.now(),
@@ -353,51 +384,75 @@ var OpenCodeClient = class {
353
384
  });
354
385
  }
355
386
  async resolvePromptResponse(input, data, knownMessageIds) {
356
- if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
357
- const messageId = data.info?.id;
387
+ const structured = input.structured ?? false;
388
+ if (!shouldPollPromptMessage(data, structured)) return data;
389
+ const messageId = extractMessageId(data.info);
390
+ let expectedParentId = toAssistantMessage(data.info)?.parentID ?? null;
358
391
  let bestCandidate = data;
359
- if (!messageId) return await this.findLatestPromptResponse(input.sessionId, {
360
- initialMessageId: null,
361
- knownMessageIds,
362
- structured: input.structured ?? false
363
- }) ?? data;
392
+ let hasWaited = false;
364
393
  for (const delayMs of PROMPT_MESSAGE_POLL_DELAYS_MS) {
365
- if (delayMs > 0) await delay(delayMs);
366
- const next = await this.fetchPromptMessage(input.sessionId, messageId);
367
- if (!next) {
368
- const latest = await this.findLatestPromptResponse(input.sessionId, {
369
- initialMessageId: messageId,
370
- knownMessageIds,
371
- structured: input.structured ?? false
372
- });
373
- if (latest) {
374
- bestCandidate = latest;
375
- if (!shouldPollPromptMessage(bestCandidate, input.structured ?? false)) return bestCandidate;
394
+ if (delayMs > 0) {
395
+ await delay(delayMs);
396
+ hasWaited = true;
397
+ }
398
+ if (messageId) {
399
+ const next = await this.fetchPromptMessage(input.sessionId, messageId);
400
+ if (next) {
401
+ bestCandidate = selectPromptResponseCandidate([bestCandidate, next], {
402
+ initialMessageId: messageId,
403
+ initialParentId: expectedParentId,
404
+ knownMessageIds,
405
+ structured
406
+ }) ?? bestCandidate;
407
+ expectedParentId = toAssistantMessage(next.info)?.parentID ?? expectedParentId;
408
+ if (!shouldPollPromptMessage(next, structured)) return next;
376
409
  }
377
- continue;
378
410
  }
379
- data = next;
380
- bestCandidate = next;
381
- if (!shouldPollPromptMessage(data, input.structured ?? false)) return data;
382
411
  const latest = await this.findLatestPromptResponse(input.sessionId, {
383
412
  initialMessageId: messageId,
413
+ initialParentId: expectedParentId,
384
414
  knownMessageIds,
385
- structured: input.structured ?? false
415
+ structured
386
416
  });
387
417
  if (latest) {
388
- bestCandidate = latest;
389
- if (!shouldPollPromptMessage(bestCandidate, input.structured ?? false)) return bestCandidate;
418
+ bestCandidate = selectPromptResponseCandidate([bestCandidate, latest], {
419
+ initialMessageId: messageId,
420
+ initialParentId: expectedParentId,
421
+ knownMessageIds,
422
+ structured
423
+ }) ?? bestCandidate;
424
+ expectedParentId = toAssistantMessage(latest.info)?.parentID ?? expectedParentId;
425
+ if (!shouldPollPromptMessage(latest, structured)) return latest;
390
426
  }
427
+ if ((await this.fetchPromptSessionStatus(input.sessionId))?.type === "idle" && hasWaited) break;
391
428
  }
392
- return bestCandidate;
429
+ const latest = await this.findLatestPromptResponse(input.sessionId, {
430
+ initialMessageId: messageId,
431
+ initialParentId: expectedParentId,
432
+ knownMessageIds,
433
+ structured
434
+ });
435
+ return selectPromptResponseCandidate([bestCandidate, latest], {
436
+ initialMessageId: messageId,
437
+ initialParentId: expectedParentId,
438
+ knownMessageIds,
439
+ structured
440
+ }) ?? bestCandidate;
393
441
  }
394
442
  async fetchPromptMessage(sessionId, messageId) {
395
- if (typeof this.client.session.message !== "function") return null;
396
443
  try {
397
- return unwrapSdkData(await this.client.session.message({
444
+ if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", {
445
+ url: "/session/{sessionID}/message/{messageID}",
446
+ path: {
447
+ sessionID: sessionId,
448
+ messageID: messageId
449
+ }
450
+ }));
451
+ if (typeof this.client.session.message !== "function") return null;
452
+ return normalizePromptResponse(unwrapSdkData(await this.client.session.message({
398
453
  sessionID: sessionId,
399
454
  messageID: messageId
400
- }, SDK_OPTIONS));
455
+ }, SDK_OPTIONS)));
401
456
  } catch {
402
457
  return null;
403
458
  }
@@ -405,16 +460,27 @@ var OpenCodeClient = class {
405
460
  async captureKnownMessageIds(sessionId) {
406
461
  const messages = await this.fetchRecentPromptMessages(sessionId);
407
462
  if (!messages) return /* @__PURE__ */ new Set();
408
- return new Set(messages.map((message) => message.info?.id).filter((id) => typeof id === "string" && id.length > 0));
463
+ return new Set(messages.map((message) => extractMessageId(message.info)).filter((id) => typeof id === "string" && id.length > 0));
409
464
  }
410
465
  async fetchRecentPromptMessages(sessionId) {
411
- if (typeof this.client.session.messages !== "function") return null;
412
466
  try {
413
- const data = unwrapSdkData(await this.client.session.messages({
467
+ if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", {
468
+ url: "/session/{sessionID}/message",
469
+ path: { sessionID: sessionId },
470
+ query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
471
+ }));
472
+ if (typeof this.client.session.messages !== "function") return null;
473
+ return normalizePromptResponses(unwrapSdkData(await this.client.session.messages({
414
474
  sessionID: sessionId,
415
475
  limit: PROMPT_MESSAGE_POLL_LIMIT
416
- }, SDK_OPTIONS));
417
- return Array.isArray(data) ? data : null;
476
+ }, SDK_OPTIONS)));
477
+ } catch {
478
+ return null;
479
+ }
480
+ }
481
+ async fetchPromptSessionStatus(sessionId) {
482
+ try {
483
+ return (await this.getSessionStatuses())[sessionId] ?? null;
418
484
  } catch {
419
485
  return null;
420
486
  }
@@ -422,24 +488,10 @@ var OpenCodeClient = class {
422
488
  async findLatestPromptResponse(sessionId, options) {
423
489
  const messages = await this.fetchRecentPromptMessages(sessionId);
424
490
  if (!messages || messages.length === 0) return null;
425
- const candidates = messages.filter((message) => toAssistantMessage(message.info) !== null).map((message) => {
426
- const assistant = toAssistantMessage(message.info);
427
- const id = assistant?.id ?? null;
428
- return {
429
- createdAt: typeof assistant?.time?.created === "number" && Number.isFinite(assistant.time.created) ? assistant.time.created : 0,
430
- id,
431
- isInitial: !!id && id === options.initialMessageId,
432
- isNew: !!id && !options.knownMessageIds.has(id),
433
- isUsable: !shouldPollPromptMessage(message, options.structured),
434
- message
435
- };
436
- }).sort((left, right) => Number(right.isUsable) - Number(left.isUsable) || Number(right.isNew) - Number(left.isNew) || Number(right.isInitial) - Number(left.isInitial) || right.createdAt - left.createdAt);
437
- return (candidates.find((candidate) => candidate.isUsable && (candidate.isNew || candidate.isInitial)) ?? candidates.find((candidate) => candidate.isNew || candidate.isInitial) ?? null)?.message ?? null;
491
+ return selectPromptResponseCandidate(messages, options);
438
492
  }
439
493
  async loadModels() {
440
- const [configResponse, providersResponse] = await Promise.all([this.client.config.get(void 0, SDK_OPTIONS), this.client.config.providers(void 0, SDK_OPTIONS)]);
441
- const config = unwrapSdkData(configResponse);
442
- const providerCatalog = unwrapSdkData(providersResponse);
494
+ const [config, providerCatalog] = await Promise.all([this.loadConfig(), this.loadProviderCatalog()]);
443
495
  const providerAvailability = await resolveProviderAvailability(config, this.fetchFn);
444
496
  const models = buildSelectableModels(config, providerCatalog.providers, providerAvailability);
445
497
  this.modelCache = {
@@ -449,6 +501,43 @@ var OpenCodeClient = class {
449
501
  };
450
502
  return models;
451
503
  }
504
+ async loadConfig() {
505
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config" });
506
+ return unwrapSdkData(await this.client.config.get(void 0, SDK_OPTIONS));
507
+ }
508
+ async loadProviderCatalog() {
509
+ if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config/providers" });
510
+ return unwrapSdkData(await this.client.config.providers(void 0, SDK_OPTIONS));
511
+ }
512
+ async sendPromptRequest(input, parts) {
513
+ if (hasRawSdkMethod(this.client, "post")) return normalizePromptResponse(await this.requestRaw("post", {
514
+ url: "/session/{sessionID}/message",
515
+ path: { sessionID: input.sessionId },
516
+ body: {
517
+ ...input.agent ? { agent: input.agent } : {},
518
+ ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
519
+ ...input.model ? { model: input.model } : {},
520
+ ...input.variant ? { variant: input.variant } : {},
521
+ parts
522
+ }
523
+ }));
524
+ return normalizePromptResponse(unwrapSdkData(await this.client.session.prompt({
525
+ sessionID: input.sessionId,
526
+ ...input.agent ? { agent: input.agent } : {},
527
+ ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
528
+ ...input.model ? { model: input.model } : {},
529
+ ...input.variant ? { variant: input.variant } : {},
530
+ parts
531
+ }, SDK_OPTIONS)));
532
+ }
533
+ async requestRaw(method, options) {
534
+ const handler = getRawSdkClient(this.client)?.[method];
535
+ if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible raw ${method.toUpperCase()} method.`);
536
+ return unwrapSdkData(await handler({
537
+ ...SDK_OPTIONS,
538
+ ...options
539
+ }));
540
+ }
452
541
  };
453
542
  function createOpenCodeClientFromSdkClient(client, fetchFn = fetch) {
454
543
  return new OpenCodeClient(void 0, client, fetchFn);
@@ -539,7 +628,8 @@ function extractTextFromParts(parts) {
539
628
  }
540
629
  function buildPromptSessionResult(data, options) {
541
630
  const assistantInfo = toAssistantMessage(data.info);
542
- const bodyMd = options.structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
631
+ const structuredPayload = extractStructuredPayload(assistantInfo);
632
+ const bodyMd = options.structured ? extractStructuredMarkdown(structuredPayload) : null;
543
633
  const responseParts = Array.isArray(data.parts) ? data.parts : [];
544
634
  const fallbackText = extractTextFromParts(responseParts) || bodyMd || options.emptyResponseText;
545
635
  return {
@@ -549,20 +639,72 @@ function buildPromptSessionResult(data, options) {
549
639
  info: assistantInfo,
550
640
  metrics: extractPromptMetrics(assistantInfo, options.startedAt, options.finishedAt),
551
641
  parts: responseParts,
552
- structured: assistantInfo?.structured ?? null
642
+ structured: structuredPayload ?? null
553
643
  };
554
644
  }
555
645
  function shouldPollPromptMessage(data, structured) {
556
646
  const assistantInfo = toAssistantMessage(data.info);
557
- const bodyMd = structured ? extractStructuredMarkdown(assistantInfo?.structured) : null;
647
+ const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
558
648
  const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
559
649
  const hasAssistantError = !!assistantInfo?.error;
560
- const isCompleted = typeof assistantInfo?.time?.completed === "number" && Number.isFinite(assistantInfo.time.completed);
561
- return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
650
+ return !hasText && !bodyMd && !hasAssistantError;
651
+ }
652
+ function normalizePromptResponse(response) {
653
+ return {
654
+ info: isPlainRecord(response?.info) ? response.info : null,
655
+ parts: normalizePromptParts(response?.parts)
656
+ };
657
+ }
658
+ function normalizePromptResponses(responses) {
659
+ if (!Array.isArray(responses)) return null;
660
+ return responses.map((response) => normalizePromptResponse(response));
661
+ }
662
+ function normalizePromptParts(parts) {
663
+ return Array.isArray(parts) ? parts : [];
562
664
  }
563
665
  function toAssistantMessage(message) {
564
666
  if (!message || typeof message !== "object") return null;
565
- return !("role" in message) || message.role === "assistant" ? message : null;
667
+ if ("role" in message && message.role !== "assistant") return null;
668
+ const normalized = {};
669
+ if ("agent" in message && typeof message.agent === "string" && message.agent.trim().length > 0) normalized.agent = message.agent;
670
+ if ("cost" in message && typeof message.cost === "number" && Number.isFinite(message.cost)) normalized.cost = message.cost;
671
+ const error = normalizeAssistantError("error" in message ? message.error : void 0);
672
+ if (error) normalized.error = error;
673
+ if ("finish" in message && typeof message.finish === "string" && message.finish.trim().length > 0) normalized.finish = message.finish;
674
+ if ("id" in message && typeof message.id === "string" && message.id.trim().length > 0) normalized.id = message.id;
675
+ if ("mode" in message && typeof message.mode === "string" && message.mode.trim().length > 0) normalized.mode = message.mode;
676
+ if ("modelID" in message && typeof message.modelID === "string" && message.modelID.trim().length > 0) normalized.modelID = message.modelID;
677
+ if ("parentID" in message && typeof message.parentID === "string" && message.parentID.trim().length > 0) normalized.parentID = message.parentID;
678
+ if ("path" in message && isPlainRecord(message.path)) normalized.path = {
679
+ ...typeof message.path.cwd === "string" && message.path.cwd.trim().length > 0 ? { cwd: message.path.cwd } : {},
680
+ ...typeof message.path.root === "string" && message.path.root.trim().length > 0 ? { root: message.path.root } : {}
681
+ };
682
+ if ("providerID" in message && typeof message.providerID === "string" && message.providerID.trim().length > 0) normalized.providerID = message.providerID;
683
+ if ("role" in message && message.role === "assistant") normalized.role = "assistant";
684
+ if ("sessionID" in message && typeof message.sessionID === "string" && message.sessionID.trim().length > 0) normalized.sessionID = message.sessionID;
685
+ const structuredPayload = extractStructuredPayload(message);
686
+ if (structuredPayload !== null) normalized.structured = structuredPayload;
687
+ if ("summary" in message && typeof message.summary === "boolean") normalized.summary = message.summary;
688
+ if ("time" in message && isPlainRecord(message.time)) normalized.time = {
689
+ ...typeof message.time.created === "number" && Number.isFinite(message.time.created) ? { created: message.time.created } : {},
690
+ ...typeof message.time.completed === "number" && Number.isFinite(message.time.completed) ? { completed: message.time.completed } : {}
691
+ };
692
+ if ("tokens" in message && isPlainRecord(message.tokens)) normalized.tokens = {
693
+ ...typeof message.tokens.input === "number" && Number.isFinite(message.tokens.input) ? { input: message.tokens.input } : {},
694
+ ...typeof message.tokens.output === "number" && Number.isFinite(message.tokens.output) ? { output: message.tokens.output } : {},
695
+ ...typeof message.tokens.reasoning === "number" && Number.isFinite(message.tokens.reasoning) ? { reasoning: message.tokens.reasoning } : {},
696
+ ...typeof message.tokens.total === "number" && Number.isFinite(message.tokens.total) ? { total: message.tokens.total } : {},
697
+ ...isPlainRecord(message.tokens.cache) ? { cache: {
698
+ ...typeof message.tokens.cache.read === "number" && Number.isFinite(message.tokens.cache.read) ? { read: message.tokens.cache.read } : {},
699
+ ...typeof message.tokens.cache.write === "number" && Number.isFinite(message.tokens.cache.write) ? { write: message.tokens.cache.write } : {}
700
+ } } : {}
701
+ };
702
+ if ("variant" in message && typeof message.variant === "string" && message.variant.trim().length > 0) normalized.variant = message.variant;
703
+ return normalized;
704
+ }
705
+ function extractMessageId(message) {
706
+ if (!isPlainRecord(message)) return null;
707
+ return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
566
708
  }
567
709
  function delay(ms) {
568
710
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -622,6 +764,47 @@ function unwrapSdkData(response) {
622
764
  function getRawSdkClient(client) {
623
765
  return client.client ?? client._client ?? null;
624
766
  }
767
+ function hasRawSdkMethod(client, method) {
768
+ return typeof getRawSdkClient(client)?.[method] === "function";
769
+ }
770
+ function normalizeAssistantError(value) {
771
+ if (!isPlainRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
772
+ return {
773
+ ...value,
774
+ name: value.name,
775
+ ...isPlainRecord(value.data) ? { data: value.data } : {}
776
+ };
777
+ }
778
+ function extractStructuredPayload(message) {
779
+ if (!isPlainRecord(message)) return null;
780
+ if ("structured" in message && message.structured !== void 0) return message.structured;
781
+ if ("structured_output" in message && message.structured_output !== void 0) return message.structured_output;
782
+ return null;
783
+ }
784
+ function selectPromptResponseCandidate(candidates, options) {
785
+ const availableCandidates = candidates.filter((candidate) => !!candidate).filter((candidate) => toAssistantMessage(candidate.info) !== null);
786
+ if (availableCandidates.length === 0) return null;
787
+ return [...availableCandidates].sort((left, right) => comparePromptResponseCandidates(left, right, options))[0] ?? null;
788
+ }
789
+ function comparePromptResponseCandidates(left, right, options) {
790
+ const leftRank = getPromptResponseCandidateRank(left, options);
791
+ const rightRank = getPromptResponseCandidateRank(right, options);
792
+ return Number(rightRank.isUsable) - Number(leftRank.isUsable) || Number(rightRank.isInitial) - Number(leftRank.isInitial) || Number(rightRank.sharesParent) - Number(leftRank.sharesParent) || Number(rightRank.isNew) - Number(leftRank.isNew) || rightRank.createdAt - leftRank.createdAt;
793
+ }
794
+ function getPromptResponseCandidateRank(message, options) {
795
+ const assistant = toAssistantMessage(message.info);
796
+ const id = assistant?.id ?? null;
797
+ return {
798
+ createdAt: typeof assistant?.time?.created === "number" && Number.isFinite(assistant.time.created) ? assistant.time.created : 0,
799
+ isInitial: !!id && id === options.initialMessageId,
800
+ isNew: !!id && !options.knownMessageIds.has(id),
801
+ isUsable: !shouldPollPromptMessage(message, options.structured),
802
+ sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId
803
+ };
804
+ }
805
+ function isPlainRecord(value) {
806
+ return value !== null && typeof value === "object" && !Array.isArray(value);
807
+ }
625
808
  async function resolveProviderAvailability(config, fetchFn) {
626
809
  const configuredProviders = Object.entries(config.provider ?? {});
627
810
  const availabilityEntries = await Promise.all(configuredProviders.map(async ([providerId, providerConfig]) => [providerId, await fetchProviderAvailableModelIds(providerConfig, fetchFn)]));