opencode-tbot 0.1.9 → 0.1.11

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