opencode-tbot 0.1.25 → 0.1.26

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
@@ -1,4 +1,4 @@
1
- import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-CCeFjxSf.js";
1
+ import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-DNeV2Ckw.js";
2
2
  import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
4
  import { parse, printParseErrorCode } from "jsonc-parser";
@@ -227,11 +227,13 @@ var PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS = [
227
227
  1e3
228
228
  ];
229
229
  var PROMPT_MESSAGE_POLL_INTERVAL_MS = 2e3;
230
- var PROMPT_POLL_REQUEST_TIMEOUT_MS = 15e3;
231
- var PROMPT_SEND_TIMEOUT_MS = 3e4;
232
- var PROMPT_MESSAGE_POLL_TIMEOUT_MS = 6e4;
233
230
  var PROMPT_MESSAGE_POLL_LIMIT = 20;
234
231
  var PROMPT_LOG_SERVICE = "opencode-tbot";
232
+ var DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY = {
233
+ pollRequestTimeoutMs: 15e3,
234
+ recoveryInactivityTimeoutMs: 12e4,
235
+ waitTimeoutMs: 18e5
236
+ };
235
237
  var STRUCTURED_REPLY_SCHEMA = {
236
238
  type: "json_schema",
237
239
  retryCount: 2,
@@ -253,20 +255,39 @@ var StructuredReplySchema = z.object({ body_md: z.string() });
253
255
  var OpenCodeClient = class {
254
256
  client;
255
257
  fetchFn;
256
- promptRequestTimeouts = {
257
- pollRequestMs: PROMPT_POLL_REQUEST_TIMEOUT_MS,
258
- sendMs: PROMPT_SEND_TIMEOUT_MS,
259
- totalPollMs: PROMPT_MESSAGE_POLL_TIMEOUT_MS
260
- };
258
+ sdkFlavor;
259
+ promptTimeoutPolicy;
261
260
  modelCache = {
262
261
  expiresAt: 0,
263
262
  promise: null,
264
263
  value: null
265
264
  };
266
- constructor(options, client, fetchFn = fetch) {
265
+ constructor(options, client, fetchFn = fetch, promptTimeoutPolicy = {}) {
267
266
  if (!options && !client) throw new Error("OpenCodeClient requires either base URL options or an injected SDK client.");
268
267
  this.client = client ?? createOpencodeClient(buildOpenCodeSdkConfig(options));
269
268
  this.fetchFn = fetchFn;
269
+ this.sdkFlavor = detectSdkFlavor(this.client);
270
+ this.promptTimeoutPolicy = resolvePromptTimeoutPolicy(promptTimeoutPolicy);
271
+ }
272
+ configurePromptTimeoutPolicy(promptTimeoutPolicy) {
273
+ this.promptTimeoutPolicy = resolvePromptTimeoutPolicy({
274
+ ...this.promptTimeoutPolicy,
275
+ ...promptTimeoutPolicy
276
+ });
277
+ }
278
+ async callScopedSdkMethod(scope, method, input) {
279
+ const target = this.client[scope];
280
+ const handler = target?.[method];
281
+ if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible ${scope}.${method} method.`);
282
+ if (this.sdkFlavor === "legacy") return unwrapSdkData(await handler.call(target, {
283
+ ...SDK_OPTIONS,
284
+ ...input.signal ? { signal: input.signal } : {},
285
+ ...input.legacyOptions ?? {}
286
+ }));
287
+ return unwrapSdkData(await handler.call(target, input.v2Parameters, {
288
+ ...SDK_OPTIONS,
289
+ ...input.signal ? { signal: input.signal } : {}
290
+ }));
270
291
  }
271
292
  async getHealth() {
272
293
  const rawClient = getRawSdkClient(this.client);
@@ -277,62 +298,70 @@ var OpenCodeClient = class {
277
298
  return this.requestRaw("get", { url: "/global/health" });
278
299
  }
279
300
  async abortSession(sessionId) {
280
- if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
281
- url: "/session/{sessionID}/abort",
282
- path: { sessionID: sessionId }
301
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", buildRawSessionRequest(this.sdkFlavor, sessionId, "/abort"));
302
+ return this.callScopedSdkMethod("session", "abort", {
303
+ legacyOptions: { path: { id: sessionId } },
304
+ v2Parameters: { sessionID: sessionId }
283
305
  });
284
- return unwrapSdkData(await this.client.session.abort({ sessionID: sessionId }, SDK_OPTIONS));
285
306
  }
286
307
  async deleteSession(sessionId) {
287
- if (hasRawSdkMethod(this.client, "delete")) return this.requestRaw("delete", {
288
- url: "/session/{sessionID}",
289
- path: { sessionID: sessionId }
308
+ if (hasRawSdkMethod(this.client, "delete")) return this.requestRaw("delete", buildRawSessionRequest(this.sdkFlavor, sessionId));
309
+ return this.callScopedSdkMethod("session", "delete", {
310
+ legacyOptions: { path: { id: sessionId } },
311
+ v2Parameters: { sessionID: sessionId }
290
312
  });
291
- return unwrapSdkData(await this.client.session.delete({ sessionID: sessionId }, SDK_OPTIONS));
292
313
  }
293
314
  async forkSession(sessionId, messageId) {
294
- if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
295
- url: "/session/{sessionID}/fork",
296
- path: { sessionID: sessionId },
297
- ...messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}
315
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", buildRawSessionRequest(this.sdkFlavor, sessionId, "/fork", messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}));
316
+ return this.callScopedSdkMethod("session", "fork", {
317
+ legacyOptions: {
318
+ path: { id: sessionId },
319
+ ...messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}
320
+ },
321
+ v2Parameters: {
322
+ sessionID: sessionId,
323
+ ...messageId?.trim() ? { messageID: messageId.trim() } : {}
324
+ }
298
325
  });
299
- return unwrapSdkData(await this.client.session.fork({
300
- sessionID: sessionId,
301
- ...messageId?.trim() ? { messageID: messageId.trim() } : {}
302
- }, SDK_OPTIONS));
303
326
  }
304
327
  async getPath() {
305
328
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/path" });
306
- return unwrapSdkData(await this.client.path.get(void 0, SDK_OPTIONS));
329
+ return this.callScopedSdkMethod("path", "get", {});
307
330
  }
308
331
  async listLspStatuses(directory) {
309
332
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
310
333
  url: "/lsp",
311
334
  ...directory ? { query: { directory } } : {}
312
335
  });
313
- return unwrapSdkData(await this.client.lsp.status(directory ? { directory } : void 0, SDK_OPTIONS));
336
+ return this.callScopedSdkMethod("lsp", "status", {
337
+ legacyOptions: directory ? { query: { directory } } : void 0,
338
+ v2Parameters: directory ? { directory } : void 0
339
+ });
314
340
  }
315
341
  async listMcpStatuses(directory) {
316
342
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
317
343
  url: "/mcp",
318
344
  ...directory ? { query: { directory } } : {}
319
345
  });
320
- return unwrapSdkData(await this.client.mcp.status(directory ? { directory } : void 0, SDK_OPTIONS));
346
+ return this.callScopedSdkMethod("mcp", "status", {
347
+ legacyOptions: directory ? { query: { directory } } : void 0,
348
+ v2Parameters: directory ? { directory } : void 0
349
+ });
321
350
  }
322
351
  async getSessionStatuses() {
323
352
  return this.loadSessionStatuses();
324
353
  }
325
354
  async listProjects() {
326
355
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project" });
327
- return unwrapSdkData(await this.client.project.list(void 0, SDK_OPTIONS));
356
+ return this.callScopedSdkMethod("project", "list", {});
328
357
  }
329
358
  async listSessions() {
330
359
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session" });
331
- return unwrapSdkData(await this.client.session.list(void 0, SDK_OPTIONS));
360
+ return this.callScopedSdkMethod("session", "list", {});
332
361
  }
333
362
  async getCurrentProject() {
334
363
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project/current" });
335
- return unwrapSdkData(await this.client.project.current(void 0, SDK_OPTIONS));
364
+ return this.callScopedSdkMethod("project", "current", {});
336
365
  }
337
366
  async createSessionForDirectory(directory, title) {
338
367
  if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
@@ -340,34 +369,57 @@ var OpenCodeClient = class {
340
369
  query: { directory },
341
370
  ...title ? { body: { title } } : {}
342
371
  });
343
- return unwrapSdkData(await this.client.session.create(title ? {
344
- directory,
345
- title
346
- } : { directory }, SDK_OPTIONS));
372
+ return this.callScopedSdkMethod("session", "create", {
373
+ legacyOptions: {
374
+ query: { directory },
375
+ ...title ? { body: { title } } : {}
376
+ },
377
+ v2Parameters: title ? {
378
+ directory,
379
+ title
380
+ } : { directory }
381
+ });
347
382
  }
348
383
  async renameSession(sessionId, title) {
349
- if (hasRawSdkMethod(this.client, "patch")) return this.requestRaw("patch", {
350
- url: "/session/{sessionID}",
351
- path: { sessionID: sessionId },
352
- body: { title }
384
+ if (hasRawSdkMethod(this.client, "patch")) return this.requestRaw("patch", buildRawSessionRequest(this.sdkFlavor, sessionId, "", { body: { title } }));
385
+ return this.callScopedSdkMethod("session", "update", {
386
+ legacyOptions: {
387
+ path: { id: sessionId },
388
+ body: { title }
389
+ },
390
+ v2Parameters: {
391
+ sessionID: sessionId,
392
+ title
393
+ }
353
394
  });
354
- return unwrapSdkData(await this.client.session.update({
355
- sessionID: sessionId,
356
- title
357
- }, SDK_OPTIONS));
358
395
  }
359
396
  async listAgents() {
360
397
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/agent" });
361
- return unwrapSdkData(await this.client.app.agents(void 0, SDK_OPTIONS));
398
+ return this.callScopedSdkMethod("app", "agents", {});
362
399
  }
363
400
  async listPendingPermissions(directory) {
364
- if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
401
+ if (this.sdkFlavor === "v2" && hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
365
402
  url: "/permission",
366
403
  ...directory ? { query: { directory } } : {}
367
404
  });
368
- return unwrapSdkData(await this.client.permission.list(directory ? { directory } : void 0, SDK_OPTIONS));
405
+ return this.callScopedSdkMethod("permission", "list", {
406
+ legacyOptions: directory ? { query: { directory } } : void 0,
407
+ v2Parameters: directory ? { directory } : void 0
408
+ });
369
409
  }
370
- async replyToPermission(requestId, reply, message) {
410
+ async replyToPermission(requestId, reply, message, sessionId) {
411
+ if (this.sdkFlavor === "legacy" && sessionId) {
412
+ if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", buildRawLegacyPermissionReplyRequest(sessionId, requestId, reply));
413
+ const legacyClient = this.client;
414
+ if (typeof legacyClient.postSessionIdPermissionsPermissionId === "function") return unwrapSdkData(await legacyClient.postSessionIdPermissionsPermissionId({
415
+ ...SDK_OPTIONS,
416
+ body: { response: reply },
417
+ path: {
418
+ id: sessionId,
419
+ permissionID: requestId
420
+ }
421
+ }));
422
+ }
371
423
  if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
372
424
  url: "/permission/{requestID}/reply",
373
425
  path: { requestID: requestId },
@@ -376,11 +428,20 @@ var OpenCodeClient = class {
376
428
  ...message?.trim() ? { message: message.trim() } : {}
377
429
  }
378
430
  });
379
- return unwrapSdkData(await this.client.permission.reply({
380
- requestID: requestId,
381
- reply,
382
- ...message?.trim() ? { message: message.trim() } : {}
383
- }, SDK_OPTIONS));
431
+ return this.callScopedSdkMethod("permission", "reply", {
432
+ legacyOptions: sessionId ? {
433
+ body: { response: reply },
434
+ path: {
435
+ id: sessionId,
436
+ permissionID: requestId
437
+ }
438
+ } : void 0,
439
+ v2Parameters: {
440
+ requestID: requestId,
441
+ reply,
442
+ ...message?.trim() ? { message: message.trim() } : {}
443
+ }
444
+ });
384
445
  }
385
446
  async listModels() {
386
447
  const now = Date.now();
@@ -416,7 +477,7 @@ var OpenCodeClient = class {
416
477
  }
417
478
  async resolvePromptResponse(input, data, knownMessageIds, startedAt) {
418
479
  const structured = input.structured ?? false;
419
- if (data && !shouldPollPromptMessage(data, structured)) return data;
480
+ if (data && shouldReturnPromptResponseImmediately(data, structured)) return data;
420
481
  const messageId = data ? extractMessageId(data.info) : null;
421
482
  const candidateOptions = {
422
483
  initialMessageId: messageId,
@@ -426,14 +487,18 @@ var OpenCodeClient = class {
426
487
  structured
427
488
  };
428
489
  let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
429
- const deadlineAt = Date.now() + this.promptRequestTimeouts.totalPollMs;
490
+ let lastProgressAt = Date.now();
491
+ const deadlineAt = startedAt + this.promptTimeoutPolicy.waitTimeoutMs;
430
492
  let idleStatusSeen = false;
431
493
  let attempt = 0;
432
494
  while (true) {
495
+ const remainingWaitMs = deadlineAt - Date.now();
496
+ const remainingInactivityMs = this.promptTimeoutPolicy.recoveryInactivityTimeoutMs - (Date.now() - lastProgressAt);
497
+ if (remainingWaitMs <= 0 || remainingInactivityMs <= 0) break;
433
498
  const delayMs = getPromptMessagePollDelayMs(attempt);
434
499
  attempt += 1;
435
500
  if (delayMs > 0) {
436
- const remainingMs = deadlineAt - Date.now();
501
+ const remainingMs = Math.min(remainingWaitMs, remainingInactivityMs);
437
502
  if (remainingMs <= 0) break;
438
503
  await delay(Math.min(delayMs, remainingMs));
439
504
  }
@@ -441,37 +506,59 @@ var OpenCodeClient = class {
441
506
  const next = await this.fetchPromptMessage(input.sessionId, messageId);
442
507
  if (next) {
443
508
  const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
444
- if (nextCandidate) bestCandidate = nextCandidate;
445
- if (bestCandidate && !shouldPollPromptMessage(bestCandidate, structured)) return bestCandidate;
509
+ if (nextCandidate) {
510
+ if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
511
+ lastProgressAt = Date.now();
512
+ idleStatusSeen = false;
513
+ }
514
+ bestCandidate = nextCandidate;
515
+ }
516
+ if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
446
517
  }
447
518
  }
448
519
  const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages");
449
520
  if (latest) {
450
521
  const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
451
- if (nextCandidate) bestCandidate = nextCandidate;
452
- if (bestCandidate && !shouldPollPromptMessage(bestCandidate, structured)) return bestCandidate;
522
+ if (nextCandidate) {
523
+ if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
524
+ lastProgressAt = Date.now();
525
+ idleStatusSeen = false;
526
+ }
527
+ bestCandidate = nextCandidate;
528
+ }
529
+ if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
453
530
  }
454
- if ((await this.fetchPromptSessionStatus(input.sessionId))?.type === "idle") {
531
+ const status = await this.fetchPromptSessionStatus(input.sessionId);
532
+ if (status?.type === "busy" || status?.type === "retry") {
533
+ lastProgressAt = Date.now();
534
+ idleStatusSeen = false;
535
+ } else if (status?.type === "idle") {
455
536
  if (idleStatusSeen) break;
456
537
  idleStatusSeen = true;
457
538
  }
539
+ if (bestCandidate && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
458
540
  if (Date.now() >= deadlineAt) break;
459
541
  }
460
542
  const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan");
461
543
  const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
462
544
  if (!resolved || shouldPollPromptMessage(resolved, structured)) {
545
+ const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
546
+ const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
463
547
  const error = createOpenCodePromptTimeoutError({
464
548
  sessionId: input.sessionId,
465
549
  stage: "final-scan",
466
- timeoutMs: this.promptRequestTimeouts.totalPollMs,
550
+ timeoutMs,
467
551
  messageId: messageId ?? void 0
468
552
  });
469
- this.logPromptRequestFailure(error, {
553
+ this.logPromptRequest("warn", {
554
+ lastProgressAt,
555
+ messageId: messageId ?? void 0,
556
+ sdkFlavor: this.sdkFlavor,
470
557
  sessionId: input.sessionId,
471
558
  stage: "final-scan",
472
- timeoutMs: this.promptRequestTimeouts.totalPollMs,
473
- messageId
474
- });
559
+ timeoutMs,
560
+ timeoutReason
561
+ }, "OpenCode prompt recovery timed out");
475
562
  throw error;
476
563
  }
477
564
  return resolved;
@@ -481,31 +568,28 @@ var OpenCodeClient = class {
481
568
  return await this.runPromptRequestWithTimeout({
482
569
  sessionId,
483
570
  stage: "poll-message",
484
- timeoutMs: this.promptRequestTimeouts.pollRequestMs,
571
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
485
572
  messageId
486
573
  }, async (signal) => {
487
- if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", {
488
- url: "/session/{sessionID}/message/{messageID}",
489
- path: {
574
+ if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", buildRawSessionMessageRequest(this.sdkFlavor, sessionId, messageId, { signal })));
575
+ if (typeof this.client.session.message !== "function") return null;
576
+ return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
577
+ legacyOptions: { path: {
578
+ id: sessionId,
579
+ messageID: messageId
580
+ } },
581
+ signal,
582
+ v2Parameters: {
490
583
  sessionID: sessionId,
491
584
  messageID: messageId
492
- },
493
- signal
585
+ }
494
586
  }));
495
- if (typeof this.client.session.message !== "function") return null;
496
- return normalizePromptResponse(unwrapSdkData(await this.client.session.message({
497
- sessionID: sessionId,
498
- messageID: messageId
499
- }, {
500
- ...SDK_OPTIONS,
501
- signal
502
- })));
503
587
  });
504
588
  } catch (error) {
505
589
  this.logPromptRequestFailure(error, {
506
590
  sessionId,
507
591
  stage: "poll-message",
508
- timeoutMs: this.promptRequestTimeouts.pollRequestMs,
592
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
509
593
  messageId
510
594
  });
511
595
  return null;
@@ -521,28 +605,30 @@ var OpenCodeClient = class {
521
605
  return await this.runPromptRequestWithTimeout({
522
606
  sessionId,
523
607
  stage,
524
- timeoutMs: this.promptRequestTimeouts.pollRequestMs
608
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
525
609
  }, async (signal) => {
526
- if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", {
527
- url: "/session/{sessionID}/message",
528
- path: { sessionID: sessionId },
610
+ if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", buildRawSessionRequest(this.sdkFlavor, sessionId, "/message", {
529
611
  query: { limit: PROMPT_MESSAGE_POLL_LIMIT },
530
612
  signal
531
- }));
532
- if (typeof this.client.session.messages !== "function") return null;
533
- return normalizePromptResponses(unwrapSdkData(await this.client.session.messages({
534
- sessionID: sessionId,
535
- limit: PROMPT_MESSAGE_POLL_LIMIT
536
- }, {
537
- ...SDK_OPTIONS,
538
- signal
539
613
  })));
614
+ if (typeof this.client.session.messages !== "function") return null;
615
+ return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
616
+ legacyOptions: {
617
+ path: { id: sessionId },
618
+ query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
619
+ },
620
+ signal,
621
+ v2Parameters: {
622
+ sessionID: sessionId,
623
+ limit: PROMPT_MESSAGE_POLL_LIMIT
624
+ }
625
+ }));
540
626
  });
541
627
  } catch (error) {
542
628
  this.logPromptRequestFailure(error, {
543
629
  sessionId,
544
630
  stage,
545
- timeoutMs: this.promptRequestTimeouts.pollRequestMs
631
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
546
632
  });
547
633
  return null;
548
634
  }
@@ -552,13 +638,13 @@ var OpenCodeClient = class {
552
638
  return (await this.runPromptRequestWithTimeout({
553
639
  sessionId,
554
640
  stage: "poll-status",
555
- timeoutMs: this.promptRequestTimeouts.pollRequestMs
641
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
556
642
  }, async (signal) => this.loadSessionStatuses(signal)))[sessionId] ?? null;
557
643
  } catch (error) {
558
644
  this.logPromptRequestFailure(error, {
559
645
  sessionId,
560
646
  stage: "poll-status",
561
- timeoutMs: this.promptRequestTimeouts.pollRequestMs
647
+ timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
562
648
  });
563
649
  return null;
564
650
  }
@@ -581,11 +667,11 @@ var OpenCodeClient = class {
581
667
  }
582
668
  async loadConfig() {
583
669
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config" });
584
- return unwrapSdkData(await this.client.config.get(void 0, SDK_OPTIONS));
670
+ return this.callScopedSdkMethod("config", "get", {});
585
671
  }
586
672
  async loadProviderCatalog() {
587
673
  if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config/providers" });
588
- return unwrapSdkData(await this.client.config.providers(void 0, SDK_OPTIONS));
674
+ return this.callScopedSdkMethod("config", "providers", {});
589
675
  }
590
676
  async sendPromptRequest(input, parts) {
591
677
  const requestBody = {
@@ -600,43 +686,52 @@ var OpenCodeClient = class {
600
686
  ...requestBody
601
687
  };
602
688
  try {
689
+ if (hasRawSdkMethod(this.client, "post")) return await this.runPromptRequestWithTimeout({
690
+ sessionId: input.sessionId,
691
+ stage: "send-prompt",
692
+ timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
693
+ }, async (signal) => normalizePromptResponse(await this.requestRaw("post", buildRawSessionRequest(this.sdkFlavor, input.sessionId, "/message", {
694
+ body: requestBody,
695
+ signal
696
+ }))));
697
+ if (typeof this.client.session?.prompt === "function") return await this.runPromptRequestWithTimeout({
698
+ sessionId: input.sessionId,
699
+ stage: "send-prompt",
700
+ timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
701
+ }, async (signal) => normalizePromptResponse(await this.callScopedSdkMethod("session", "prompt", {
702
+ legacyOptions: {
703
+ body: requestBody,
704
+ path: { id: input.sessionId }
705
+ },
706
+ signal,
707
+ v2Parameters: requestParameters
708
+ })));
603
709
  if (typeof this.client.session?.promptAsync === "function") {
604
710
  await this.runPromptRequestWithTimeout({
605
711
  sessionId: input.sessionId,
606
712
  stage: "send-prompt",
607
- timeoutMs: this.promptRequestTimeouts.sendMs
713
+ timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
608
714
  }, async (signal) => {
609
- await this.client.session.promptAsync(requestParameters, {
610
- ...SDK_OPTIONS,
611
- signal
715
+ await this.callScopedSdkMethod("session", "promptAsync", {
716
+ legacyOptions: {
717
+ body: requestBody,
718
+ path: { id: input.sessionId }
719
+ },
720
+ signal,
721
+ v2Parameters: requestParameters
612
722
  });
613
723
  });
614
724
  return null;
615
725
  }
616
- return await this.runPromptRequestWithTimeout({
617
- sessionId: input.sessionId,
618
- stage: "send-prompt",
619
- timeoutMs: this.promptRequestTimeouts.sendMs
620
- }, async (signal) => {
621
- if (hasRawSdkMethod(this.client, "post")) return normalizePromptResponse(await this.requestRaw("post", {
622
- url: "/session/{sessionID}/message",
623
- path: { sessionID: input.sessionId },
624
- body: requestBody,
625
- signal
626
- }));
627
- return normalizePromptResponse(unwrapSdkData(await this.client.session.prompt(requestParameters, {
628
- ...SDK_OPTIONS,
629
- signal
630
- })));
631
- });
632
726
  } catch (error) {
633
727
  this.logPromptRequestFailure(error, {
634
728
  sessionId: input.sessionId,
635
729
  stage: "send-prompt",
636
- timeoutMs: this.promptRequestTimeouts.sendMs
730
+ timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
637
731
  });
638
732
  throw error;
639
733
  }
734
+ throw new Error("OpenCode SDK client does not expose a compatible prompt endpoint.");
640
735
  }
641
736
  async requestRaw(method, options) {
642
737
  const handler = getRawSdkClient(this.client)?.[method];
@@ -651,10 +746,7 @@ var OpenCodeClient = class {
651
746
  url: "/session/status",
652
747
  ...signal ? { signal } : {}
653
748
  });
654
- return unwrapSdkData(await this.client.session.status(void 0, {
655
- ...SDK_OPTIONS,
656
- ...signal ? { signal } : {}
657
- }));
749
+ return this.callScopedSdkMethod("session", "status", { signal });
658
750
  }
659
751
  async runPromptRequestWithTimeout(input, operation) {
660
752
  const startedAt = Date.now();
@@ -681,8 +773,10 @@ var OpenCodeClient = class {
681
773
  logPromptRequestFailure(error, input) {
682
774
  if (error instanceof OpenCodePromptTimeoutError) {
683
775
  this.logPromptRequest("warn", {
776
+ endpointKind: resolvePromptEndpointKind(error.data.stage),
684
777
  elapsedMs: error.data.elapsedMs,
685
778
  messageId: error.data.messageId,
779
+ sdkFlavor: this.sdkFlavor,
686
780
  sessionId: error.data.sessionId,
687
781
  stage: error.data.stage,
688
782
  timeoutMs: error.data.timeoutMs
@@ -690,8 +784,10 @@ var OpenCodeClient = class {
690
784
  return;
691
785
  }
692
786
  this.logPromptRequest("warn", {
787
+ endpointKind: resolvePromptEndpointKind(input.stage),
693
788
  error,
694
789
  messageId: input.messageId ?? void 0,
790
+ sdkFlavor: this.sdkFlavor,
695
791
  sessionId: input.sessionId,
696
792
  stage: input.stage,
697
793
  timeoutMs: input.timeoutMs
@@ -708,8 +804,8 @@ var OpenCodeClient = class {
708
804
  }).catch(() => void 0);
709
805
  }
710
806
  };
711
- function createOpenCodeClientFromSdkClient(client, fetchFn = fetch) {
712
- return new OpenCodeClient(void 0, client, fetchFn);
807
+ function createOpenCodeClientFromSdkClient(client, fetchFn = fetch, promptTimeoutPolicy = {}) {
808
+ return new OpenCodeClient(void 0, client, fetchFn, promptTimeoutPolicy);
713
809
  }
714
810
  function buildSelectableModels(config, providers, providerAvailability = /* @__PURE__ */ new Map()) {
715
811
  const configuredProviders = config.provider ?? {};
@@ -816,7 +912,14 @@ function shouldPollPromptMessage(data, structured) {
816
912
  const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
817
913
  const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
818
914
  const hasAssistantError = !!assistantInfo?.error;
819
- return !hasText && !bodyMd && !hasAssistantError;
915
+ const isCompleted = isAssistantMessageCompleted(assistantInfo);
916
+ return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
917
+ }
918
+ function shouldReturnPromptResponseImmediately(data, structured) {
919
+ return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
920
+ }
921
+ function isPromptResponseUsable(data, structured) {
922
+ return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
820
923
  }
821
924
  function normalizePromptResponse(response) {
822
925
  return {
@@ -878,6 +981,9 @@ function extractMessageId(message) {
878
981
  function delay(ms) {
879
982
  return new Promise((resolve) => setTimeout(resolve, ms));
880
983
  }
984
+ function didPromptResponseAdvance(previous, next, structured) {
985
+ return getPromptResponseProgressSignature(previous, structured) !== getPromptResponseProgressSignature(next, structured);
986
+ }
881
987
  function createOpenCodePromptTimeoutError(input) {
882
988
  return new OpenCodePromptTimeoutError({
883
989
  ...input,
@@ -885,6 +991,16 @@ function createOpenCodePromptTimeoutError(input) {
885
991
  message: input.message ?? "The OpenCode host did not finish this request in time."
886
992
  });
887
993
  }
994
+ function resolvePromptEndpointKind(stage) {
995
+ switch (stage) {
996
+ case "capture-known-messages":
997
+ case "poll-messages":
998
+ case "final-scan": return "messages";
999
+ case "poll-message": return "message";
1000
+ case "poll-status": return "status";
1001
+ default: return "prompt";
1002
+ }
1003
+ }
888
1004
  function getPromptMessagePollDelayMs(attempt) {
889
1005
  return PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS[attempt] ?? PROMPT_MESSAGE_POLL_INTERVAL_MS;
890
1006
  }
@@ -940,6 +1056,47 @@ function unwrapSdkData(response) {
940
1056
  if (response && typeof response === "object" && "data" in response) return response.data;
941
1057
  return response;
942
1058
  }
1059
+ function resolvePromptTimeoutPolicy(input) {
1060
+ return {
1061
+ pollRequestTimeoutMs: input.pollRequestTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.pollRequestTimeoutMs,
1062
+ recoveryInactivityTimeoutMs: input.recoveryInactivityTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.recoveryInactivityTimeoutMs,
1063
+ waitTimeoutMs: input.waitTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.waitTimeoutMs
1064
+ };
1065
+ }
1066
+ function detectSdkFlavor(client) {
1067
+ if (typeof client.global?.health === "function" || "permission" in client) return "v2";
1068
+ return clientExposesTwoArgumentSdkMethod(client) ? "v2" : "legacy";
1069
+ }
1070
+ function buildRawSessionRequest(sdkFlavor, sessionId, suffix = "", extra = {}) {
1071
+ return {
1072
+ url: sdkFlavor === "legacy" ? `/session/{id}${suffix}` : `/session/{sessionID}${suffix}`,
1073
+ path: sdkFlavor === "legacy" ? { id: sessionId } : { sessionID: sessionId },
1074
+ ...extra
1075
+ };
1076
+ }
1077
+ function buildRawSessionMessageRequest(sdkFlavor, sessionId, messageId, extra = {}) {
1078
+ return {
1079
+ url: sdkFlavor === "legacy" ? "/session/{id}/message/{messageID}" : "/session/{sessionID}/message/{messageID}",
1080
+ path: sdkFlavor === "legacy" ? {
1081
+ id: sessionId,
1082
+ messageID: messageId
1083
+ } : {
1084
+ sessionID: sessionId,
1085
+ messageID: messageId
1086
+ },
1087
+ ...extra
1088
+ };
1089
+ }
1090
+ function buildRawLegacyPermissionReplyRequest(sessionId, requestId, reply) {
1091
+ return {
1092
+ body: { response: reply },
1093
+ path: {
1094
+ id: sessionId,
1095
+ permissionID: requestId
1096
+ },
1097
+ url: "/session/{id}/permissions/{permissionID}"
1098
+ };
1099
+ }
943
1100
  function getRawSdkClient(client) {
944
1101
  return client.client ?? client._client ?? null;
945
1102
  }
@@ -954,6 +1111,32 @@ function normalizeAssistantError(value) {
954
1111
  ...isPlainRecord(value.data) ? { data: value.data } : {}
955
1112
  };
956
1113
  }
1114
+ function isAssistantMessageCompleted(message) {
1115
+ return !!message?.error || typeof message?.time?.completed === "number" || typeof message?.finish === "string" && message.finish.trim().length > 0;
1116
+ }
1117
+ function isCompletedEmptyPromptResponse(data, structured) {
1118
+ const assistantInfo = toAssistantMessage(data.info);
1119
+ const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
1120
+ const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
1121
+ return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
1122
+ }
1123
+ function clientExposesTwoArgumentSdkMethod(client) {
1124
+ return [
1125
+ "app",
1126
+ "config",
1127
+ "global",
1128
+ "lsp",
1129
+ "mcp",
1130
+ "path",
1131
+ "permission",
1132
+ "project",
1133
+ "session"
1134
+ ].some((scope) => {
1135
+ const target = client[scope];
1136
+ if (!target || typeof target !== "object") return false;
1137
+ return Object.values(target).some((value) => typeof value === "function" && value.length >= 2);
1138
+ });
1139
+ }
957
1140
  function extractStructuredPayload(message) {
958
1141
  if (!isPlainRecord(message)) return null;
959
1142
  if ("structured" in message && message.structured !== void 0) return message.structured;
@@ -978,7 +1161,7 @@ function getPromptResponseCandidateRank(message, options) {
978
1161
  createdAt,
979
1162
  isInitial: !!id && id === options.initialMessageId,
980
1163
  isNewSinceRequestStart: isPromptResponseNewSinceRequestStart(id, createdAt, options.knownMessageIds, options.requestStartedAt),
981
- isUsable: !shouldPollPromptMessage(message, options.structured),
1164
+ isUsable: isPromptResponseUsable(message, options.structured),
982
1165
  sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId
983
1166
  };
984
1167
  }
@@ -988,6 +1171,20 @@ function resolvePromptCandidateStartTime(startedAt, initialMessage) {
988
1171
  if (initialCreatedAt === null) return startedAt;
989
1172
  return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
990
1173
  }
1174
+ function getPromptResponseProgressSignature(response, structured) {
1175
+ if (!response) return "null";
1176
+ const assistant = toAssistantMessage(response.info);
1177
+ const responseParts = Array.isArray(response.parts) ? response.parts : [];
1178
+ return JSON.stringify({
1179
+ assistantError: assistant?.error?.name ?? null,
1180
+ bodyMd: structured ? extractStructuredMarkdown(extractStructuredPayload(assistant)) : null,
1181
+ completedAt: assistant?.time?.completed ?? null,
1182
+ finish: assistant?.finish ?? null,
1183
+ id: assistant?.id ?? null,
1184
+ partCount: responseParts.length,
1185
+ text: extractTextFromParts(responseParts)
1186
+ });
1187
+ }
991
1188
  function isPromptResponseNewSinceRequestStart(messageId, createdAt, knownMessageIds, requestStartedAt) {
992
1189
  if (!messageId || knownMessageIds.has(messageId)) return false;
993
1190
  if (requestStartedAt === null) return true;
@@ -2025,7 +2222,11 @@ function resolveExtension(mimeType) {
2025
2222
  //#region src/app/container.ts
2026
2223
  function createAppContainer(config, client) {
2027
2224
  const logger = createOpenCodeAppLogger(client, { level: config.logLevel });
2028
- return createContainer(config, createOpenCodeClientFromSdkClient(client), logger);
2225
+ return createContainer(config, createOpenCodeClientFromSdkClient(client, fetch, {
2226
+ waitTimeoutMs: config.promptWaitTimeoutMs,
2227
+ pollRequestTimeoutMs: config.promptPollRequestTimeoutMs,
2228
+ recoveryInactivityTimeoutMs: config.promptRecoveryInactivityTimeoutMs
2229
+ }), logger);
2029
2230
  }
2030
2231
  function createContainer(config, opencodeClient, logger) {
2031
2232
  const stateStore = new JsonStateStore({
@@ -4478,8 +4679,8 @@ async function handlePermissionApprovalCallback(ctx, dependencies) {
4478
4679
  const parsed = parsePermissionApprovalCallbackData(data);
4479
4680
  if (!parsed) return;
4480
4681
  try {
4481
- await dependencies.opencodeClient.replyToPermission(parsed.requestId, parsed.reply);
4482
4682
  const approval = (await dependencies.permissionApprovalRepo.listByRequestId(parsed.requestId)).find((item) => item.chatId === ctx.chat?.id);
4683
+ await dependencies.opencodeClient.replyToPermission(parsed.requestId, parsed.reply, void 0, approval?.sessionId);
4483
4684
  if (approval) await dependencies.permissionApprovalRepo.set({
4484
4685
  ...approval,
4485
4686
  status: parsed.reply,