@getpaseo/server 0.1.73 → 0.1.75

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.
Files changed (117) hide show
  1. package/dist/scripts/supervisor-entrypoint.js +2 -21
  2. package/dist/scripts/supervisor-entrypoint.js.map +1 -1
  3. package/dist/scripts/supervisor-log-config.js +31 -0
  4. package/dist/scripts/supervisor-log-config.js.map +1 -0
  5. package/dist/server/server/agent/agent-manager.d.ts +4 -1
  6. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  7. package/dist/server/server/agent/agent-manager.js +139 -14
  8. package/dist/server/server/agent/agent-manager.js.map +1 -1
  9. package/dist/server/server/agent/agent-prompt.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-prompt.js +27 -0
  11. package/dist/server/server/agent/agent-prompt.js.map +1 -1
  12. package/dist/server/server/agent/agent-sdk-types.d.ts +2 -0
  13. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  14. package/dist/server/server/agent/agent-sdk-types.js +3 -0
  15. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  16. package/dist/server/server/agent/foreground-run-state.d.ts +1 -1
  17. package/dist/server/server/agent/foreground-run-state.d.ts.map +1 -1
  18. package/dist/server/server/agent/foreground-run-state.js +2 -4
  19. package/dist/server/server/agent/foreground-run-state.js.map +1 -1
  20. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  21. package/dist/server/server/agent/provider-manifest.js +3 -3
  22. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  23. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  24. package/dist/server/server/agent/provider-registry.js +13 -5
  25. package/dist/server/server/agent/provider-registry.js.map +1 -1
  26. package/dist/server/server/agent/providers/acp-agent.d.ts +39 -1
  27. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  28. package/dist/server/server/agent/providers/acp-agent.js +121 -14
  29. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  30. package/dist/server/server/agent/providers/claude/agent.d.ts +1 -1
  31. package/dist/server/server/agent/providers/claude/agent.d.ts.map +1 -1
  32. package/dist/server/server/agent/providers/claude/agent.js +80 -24
  33. package/dist/server/server/agent/providers/claude/agent.js.map +1 -1
  34. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +7 -1
  35. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts.map +1 -1
  36. package/dist/server/server/agent/providers/codex/app-server-transport.js +28 -1
  37. package/dist/server/server/agent/providers/codex/app-server-transport.js.map +1 -1
  38. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  39. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +36 -7
  40. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  41. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +14 -4
  42. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  43. package/dist/server/server/agent/providers/codex-app-server-agent.js +121 -15
  44. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  45. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +10 -1
  46. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
  47. package/dist/server/server/agent/providers/copilot-acp-agent.js +114 -7
  48. package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -1
  49. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +1 -0
  50. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts.map +1 -1
  51. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +7 -0
  52. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js.map +1 -1
  53. package/dist/server/server/agent/providers/opencode-agent.d.ts +10 -33
  54. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  55. package/dist/server/server/agent/providers/opencode-agent.js +140 -448
  56. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  57. package/dist/server/server/agent/providers/pi-direct-agent.d.ts +1 -1
  58. package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -1
  59. package/dist/server/server/agent/providers/pi-direct-agent.js +2 -15
  60. package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -1
  61. package/dist/server/server/agent/providers/provider-runner.d.ts +1 -1
  62. package/dist/server/server/agent/providers/provider-runner.d.ts.map +1 -1
  63. package/dist/server/server/agent/providers/provider-runner.js +2 -1
  64. package/dist/server/server/agent/providers/provider-runner.js.map +1 -1
  65. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +1 -1
  66. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -1
  67. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +2 -1
  68. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -1
  69. package/dist/server/server/agent/stt-manager.d.ts +5 -1
  70. package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
  71. package/dist/server/server/agent/stt-manager.js +3 -2
  72. package/dist/server/server/agent/stt-manager.js.map +1 -1
  73. package/dist/server/server/bootstrap.d.ts +5 -0
  74. package/dist/server/server/bootstrap.d.ts.map +1 -1
  75. package/dist/server/server/bootstrap.js.map +1 -1
  76. package/dist/server/server/dictation/dictation-stream-manager.d.ts +2 -0
  77. package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -1
  78. package/dist/server/server/dictation/dictation-stream-manager.js +2 -1
  79. package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -1
  80. package/dist/server/server/logger.js +1 -1
  81. package/dist/server/server/logger.js.map +1 -1
  82. package/dist/server/server/persisted-config.d.ts +18 -0
  83. package/dist/server/server/persisted-config.d.ts.map +1 -1
  84. package/dist/server/server/persisted-config.js +2 -0
  85. package/dist/server/server/persisted-config.js.map +1 -1
  86. package/dist/server/server/schedule/service.d.ts.map +1 -1
  87. package/dist/server/server/schedule/service.js +14 -1
  88. package/dist/server/server/schedule/service.js.map +1 -1
  89. package/dist/server/server/session.d.ts +3 -0
  90. package/dist/server/server/session.d.ts.map +1 -1
  91. package/dist/server/server/session.js +89 -18
  92. package/dist/server/server/session.js.map +1 -1
  93. package/dist/server/server/speech/providers/local/config.d.ts +5 -0
  94. package/dist/server/server/speech/providers/local/config.d.ts.map +1 -1
  95. package/dist/server/server/speech/providers/local/config.js +35 -0
  96. package/dist/server/server/speech/providers/local/config.js.map +1 -1
  97. package/dist/server/server/speech/speech-config-resolver.d.ts.map +1 -1
  98. package/dist/server/server/speech/speech-config-resolver.js +1 -0
  99. package/dist/server/server/speech/speech-config-resolver.js.map +1 -1
  100. package/dist/server/server/speech/speech-runtime.d.ts +2 -0
  101. package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
  102. package/dist/server/server/speech/speech-runtime.js +2 -0
  103. package/dist/server/server/speech/speech-runtime.js.map +1 -1
  104. package/dist/server/server/voice/voice-turn-controller.d.ts +1 -0
  105. package/dist/server/server/voice/voice-turn-controller.d.ts.map +1 -1
  106. package/dist/server/server/voice/voice-turn-controller.js +1 -1
  107. package/dist/server/server/voice/voice-turn-controller.js.map +1 -1
  108. package/dist/server/server/websocket-server.d.ts.map +1 -1
  109. package/dist/server/server/websocket-server.js +2 -0
  110. package/dist/server/server/websocket-server.js.map +1 -1
  111. package/dist/src/server/agent/agent-sdk-types.js +3 -0
  112. package/dist/src/server/agent/agent-sdk-types.js.map +1 -1
  113. package/dist/src/server/agent/provider-manifest.js +3 -3
  114. package/dist/src/server/agent/provider-manifest.js.map +1 -1
  115. package/dist/src/server/persisted-config.js +2 -0
  116. package/dist/src/server/persisted-config.js.map +1 -1
  117. package/package.json +3 -3
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
  import { findExecutable, isCommandAvailable } from "../../../utils/executable.js";
5
5
  import { z } from "zod";
6
+ import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
6
7
  import { createProviderEnvSpec } from "../provider-launch-config.js";
7
8
  import { withTimeout } from "../../../utils/promise-timeout.js";
8
9
  import { execCommand } from "../../../utils/spawn.js";
@@ -24,22 +25,8 @@ const OPENCODE_CAPABILITIES = {
24
25
  const OPENCODE_BUILD_MODE_ID = "build";
25
26
  const OPENCODE_FULL_ACCESS_MODE_ID = "full-access";
26
27
  const OPENCODE_STORAGE_SESSION_LIMIT = 200;
27
- // COMPAT(opencodeEofRecovery): added in v0.1.73 to compensate for OpenCode 1.14.42+
28
- // closing the /event SSE stream cleanly after `server.connected`. Drop this whole
29
- // recovery path once OpenCode upstream restores live event delivery and the floor
30
- // version reflects that.
31
- // Upstream: anomalyco/opencode#26697 (SSE /event closes immediately after
32
- // server.connected) and anomalyco/opencode#26635 (prompt_async silently discards
33
- // requests; SSE path broken).
34
- const OPENCODE_EOF_RECOVERY_TIMEOUT_MS = 5 * 60 * 1000;
35
- const OPENCODE_EOF_RECOVERY_POLL_INTERVAL_MS = 1000;
36
- const OPENCODE_RECOVERY_ABORT_TIMEOUT_MS = 2000;
37
28
  const OPENCODE_PENDING_ABORT_START_TIMEOUT_MS = 10000;
38
- // If OpenCode silently rejects the prompt (invalid model/mode/auth), no assistant
39
- // message is ever persisted. Bound the wait so the turn fails in seconds instead
40
- // of hanging until the completion cap. Valid models normally persist their first
41
- // message within a second of LLM start, so 10s leaves comfortable headroom.
42
- const OPENCODE_EOF_RECOVERY_LIVENESS_MS = 10000;
29
+ const OPENCODE_RETRY_STATUS_FAILURE_MS = 10000;
43
30
  const DEFAULT_MODES = [
44
31
  {
45
32
  id: OPENCODE_BUILD_MODE_ID,
@@ -451,16 +438,6 @@ function mergeOpenCodeStepFinishUsage(usage, part) {
451
438
  usage.totalCostUsd = (usage.totalCostUsd ?? 0) + cost;
452
439
  }
453
440
  }
454
- function formatOpenCodeAssistantErrorMessage(error) {
455
- const data = error.data;
456
- if (data && typeof data === "object" && "message" in data) {
457
- const message = data.message;
458
- if (typeof message === "string" && message.trim().length > 0) {
459
- return message.trim();
460
- }
461
- }
462
- return error.name;
463
- }
464
441
  function hasNormalizedOpenCodeUsage(usage) {
465
442
  return [
466
443
  usage.inputTokens,
@@ -702,16 +679,11 @@ export class OpenCodeAgentClient {
702
679
  this.logger = logger.child({ module: "agent", provider: "opencode" });
703
680
  this.runtimeSettings = runtimeSettings;
704
681
  this.storageRoot = storageRoot ?? resolveOpenCodeStorageRoot();
705
- this.recovery = deps.recovery ?? {
706
- timeoutMs: OPENCODE_EOF_RECOVERY_TIMEOUT_MS,
707
- pollIntervalMs: OPENCODE_EOF_RECOVERY_POLL_INTERVAL_MS,
708
- livenessMs: OPENCODE_EOF_RECOVERY_LIVENESS_MS,
709
- };
710
682
  this.runtime =
711
683
  deps.runtime ??
712
684
  new ProductionOpenCodeRuntime(OpenCodeServerManager.getInstance(this.logger, runtimeSettings));
713
685
  }
714
- async createSession(config, _launchContext, options) {
686
+ async createSession(config, launchContext, options) {
715
687
  const openCodeConfig = this.assertConfig(config);
716
688
  const acquisition = await this.runtime.acquireServer({ force: false });
717
689
  const { url } = acquisition.server;
@@ -729,14 +701,14 @@ export class OpenCodeAgentClient {
729
701
  throw new Error("OpenCode session creation returned no data");
730
702
  }
731
703
  await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
732
- return new OpenCodeAgentSession(openCodeConfig, client, session.id, this.logger, this.storageRoot, new Map(this.modelContextWindows), acquisition.release, options?.persistSession, this.recovery);
704
+ return new OpenCodeAgentSession(openCodeConfig, client, session.id, this.logger, this.storageRoot, new Map(this.modelContextWindows), acquisition.release, options?.persistSession, launchContext?.agentId);
733
705
  }
734
706
  catch (error) {
735
707
  acquisition.release();
736
708
  throw error;
737
709
  }
738
710
  }
739
- async resumeSession(handle, overrides, _launchContext) {
711
+ async resumeSession(handle, overrides, launchContext) {
740
712
  const cwd = overrides?.cwd ?? handle.metadata?.cwd;
741
713
  if (!cwd) {
742
714
  throw new Error("OpenCode resume requires the original working directory");
@@ -755,7 +727,7 @@ export class OpenCodeAgentClient {
755
727
  });
756
728
  try {
757
729
  await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
758
- return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId, this.logger, this.storageRoot, new Map(this.modelContextWindows), acquisition.release, undefined, this.recovery);
730
+ return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId, this.logger, this.storageRoot, new Map(this.modelContextWindows), acquisition.release, undefined, launchContext?.agentId);
759
731
  }
760
732
  catch (error) {
761
733
  acquisition.release();
@@ -1592,25 +1564,23 @@ function createDeferred() {
1592
1564
  });
1593
1565
  return { promise, resolve, reject };
1594
1566
  }
1595
- const OPENCODE_TRACE_ENABLED = process.env.PASEO_OPENCODE_TRACE === "1";
1596
- function traceOpenCode(tag, data = {}) {
1597
- if (!OPENCODE_TRACE_ENABLED)
1598
- return;
1599
- const line = JSON.stringify({ ts: new Date().toISOString(), tag, ...data }, (_k, v) => {
1600
- if (v instanceof Error)
1601
- return { name: v.name, message: v.message, stack: v.stack };
1602
- if (typeof v === "bigint")
1603
- return v.toString();
1604
- return v;
1605
- });
1606
- process.stderr.write(`[opencode-trace] ${line}\n`);
1567
+ function unwrapOpenCodeGlobalEvent(event) {
1568
+ const record = readOpenCodeRecord(event);
1569
+ if (!record) {
1570
+ return null;
1571
+ }
1572
+ const payload = readOpenCodeRecord(record.payload);
1573
+ if (typeof payload?.type === "string") {
1574
+ return payload;
1575
+ }
1576
+ if (typeof record.type === "string") {
1577
+ return record;
1578
+ }
1579
+ return null;
1607
1580
  }
1608
1581
  class OpenCodeAgentSession {
1609
- constructor(config, client, sessionId, logger, storageRoot, modelContextWindowsByModelKey = new Map(), releaseServer, persistSession = true, recovery = {
1610
- timeoutMs: OPENCODE_EOF_RECOVERY_TIMEOUT_MS,
1611
- pollIntervalMs: OPENCODE_EOF_RECOVERY_POLL_INTERVAL_MS,
1612
- livenessMs: OPENCODE_EOF_RECOVERY_LIVENESS_MS,
1613
- }) {
1582
+ constructor(config, client, sessionId, logger, _storageRoot, modelContextWindowsByModelKey = new Map(), releaseServer, persistSession = true, agentId) {
1583
+ this.agentId = agentId;
1614
1584
  this.provider = "opencode";
1615
1585
  this.capabilities = OPENCODE_CAPABILITIES;
1616
1586
  this.currentMode = "default";
@@ -1637,25 +1607,15 @@ class OpenCodeAgentSession {
1637
1607
  this.subAgentCallIdByChildSessionId = new Map();
1638
1608
  this.pendingChildToolPartsBySessionId = new Map();
1639
1609
  this.deletedFromProvider = false;
1640
- this.foregroundAssistantMessageEmitted = false;
1641
- this.foregroundAssistantText = "";
1642
- this.foregroundUsageUpdated = false;
1643
- this.foregroundKnownMessageIds = new Set();
1644
- this.foregroundEmittedQuestionIds = new Set();
1645
- this.foregroundEmittedPermissionIds = new Set();
1646
- this.foregroundEmittedReasoningTextLengthByPartId = new Map();
1647
- this.foregroundEmittedToolCallSignatureByCallId = new Map();
1648
- this.foregroundTurnStartedAt = null;
1610
+ this.retryFailureTimer = null;
1649
1611
  this.config = config;
1650
1612
  this.client = client;
1651
1613
  this.sessionId = sessionId;
1652
- this.logger = logger;
1653
- this.storageRoot = storageRoot;
1614
+ this.logger = logger.child({ agentId: this.agentId });
1654
1615
  this.modelContextWindowsByModelKey = modelContextWindowsByModelKey;
1655
1616
  this.currentMode = normalizeOpenCodeModeId(config.modeId);
1656
1617
  this.releaseServer = releaseServer ?? null;
1657
1618
  this.persistSession = persistSession;
1658
- this.recovery = recovery;
1659
1619
  this.selectedModelContextWindowMaxTokens = this.resolveConfiguredModelContextWindowMaxTokens(config.model);
1660
1620
  }
1661
1621
  get id() {
@@ -1739,19 +1699,11 @@ class OpenCodeAgentSession {
1739
1699
  throw new Error("A foreground turn is already active");
1740
1700
  }
1741
1701
  await this.awaitPendingAbortBeforeStartingTurn();
1742
- this.foregroundTurnStartedAt = Date.now();
1743
1702
  this.runningToolCalls.clear();
1744
1703
  this.subAgentsByCallId.clear();
1745
1704
  this.subAgentCallIdByChildSessionId.clear();
1746
1705
  this.pendingChildToolPartsBySessionId.clear();
1747
- this.foregroundAssistantMessageEmitted = false;
1748
- this.foregroundAssistantText = "";
1749
- this.foregroundUsageUpdated = false;
1750
- this.foregroundEmittedQuestionIds.clear();
1751
- this.foregroundEmittedPermissionIds.clear();
1752
- this.foregroundEmittedReasoningTextLengthByPartId.clear();
1753
- this.foregroundEmittedToolCallSignatureByCallId.clear();
1754
- this.foregroundKnownMessageIds = await this.readPersistedSessionMessageIds();
1706
+ this.clearRetryFailureTimer();
1755
1707
  const turnAbortController = new AbortController();
1756
1708
  this.abortController = turnAbortController;
1757
1709
  await this.ensureMcpServersConfigured();
@@ -1860,7 +1812,7 @@ class OpenCodeAgentSession {
1860
1812
  // SDK input validation) is caught alongside async rejections. A plain
1861
1813
  // `.then().catch()` chain would let a sync throw escape unhandled.
1862
1814
  void (async () => {
1863
- traceOpenCode("promptAsync.start", {
1815
+ this.traceOpenCode("provider.opencode.prompt_async.start", {
1864
1816
  turnId,
1865
1817
  sessionId: this.sessionId,
1866
1818
  model,
@@ -1886,7 +1838,7 @@ class OpenCodeAgentSession {
1886
1838
  ...(effectiveMode ? { agent: effectiveMode } : {}),
1887
1839
  ...(effectiveVariant ? { variant: effectiveVariant } : {}),
1888
1840
  });
1889
- traceOpenCode("promptAsync.response", {
1841
+ this.traceOpenCode("provider.opencode.prompt_async.response", {
1890
1842
  turnId,
1891
1843
  hasError: promptResponse.error !== undefined,
1892
1844
  error: promptResponse.error,
@@ -1901,7 +1853,7 @@ class OpenCodeAgentSession {
1901
1853
  }
1902
1854
  }
1903
1855
  catch (error) {
1904
- traceOpenCode("promptAsync.throw", {
1856
+ this.traceOpenCode("provider.opencode.prompt_async.throw", {
1905
1857
  turnId,
1906
1858
  error: error instanceof Error
1907
1859
  ? { name: error.name, message: error.message, stack: error.stack }
@@ -1924,66 +1876,49 @@ class OpenCodeAgentSession {
1924
1876
  };
1925
1877
  }
1926
1878
  async consumeEventStream(turnId, turnAbortController, subscriptionReady) {
1927
- traceOpenCode("subscribe.start", { turnId, sessionId: this.sessionId, cwd: this.config.cwd });
1879
+ this.traceOpenCode("provider.opencode.subscribe.start", {
1880
+ turnId,
1881
+ sessionId: this.sessionId,
1882
+ cwd: this.config.cwd,
1883
+ });
1928
1884
  try {
1929
- const result = await this.client.event.subscribe({ directory: this.config.cwd }, { signal: turnAbortController.signal });
1930
- traceOpenCode("subscribe.ready", { turnId, sessionId: this.sessionId });
1931
- subscriptionReady.resolve();
1885
+ const result = await this.client.global.event({
1886
+ signal: turnAbortController.signal,
1887
+ sseMaxRetryAttempts: 0,
1888
+ });
1932
1889
  let eventCount = 0;
1933
- for await (const event of result.stream) {
1890
+ let subscriptionReadyResolved = false;
1891
+ for await (const rawEvent of result.stream) {
1934
1892
  eventCount += 1;
1935
- traceOpenCode("event.raw", {
1936
- turnId,
1937
- n: eventCount,
1938
- type: event.type,
1939
- properties: event.properties,
1940
- });
1941
- if (turnAbortController.signal.aborted || this.activeForegroundTurnId !== turnId) {
1942
- traceOpenCode("event.skip", {
1893
+ if (!subscriptionReadyResolved) {
1894
+ subscriptionReadyResolved = true;
1895
+ this.traceOpenCode("provider.opencode.subscribe.ready", {
1943
1896
  turnId,
1944
- n: eventCount,
1945
- aborted: turnAbortController.signal.aborted,
1946
- activeTurnId: this.activeForegroundTurnId,
1897
+ sessionId: this.sessionId,
1947
1898
  });
1948
- break;
1899
+ subscriptionReady.resolve();
1949
1900
  }
1950
- const translated = await this.translateEvent(event);
1951
- traceOpenCode("event.translated", {
1901
+ const shouldContinue = await this.consumeOpenCodeStreamEvent({
1902
+ rawEvent,
1903
+ eventCount,
1952
1904
  turnId,
1953
- n: eventCount,
1954
- count: translated.length,
1955
- types: translated.map((t) => t.type),
1905
+ turnAbortController,
1956
1906
  });
1957
- for (const e of translated) {
1958
- if (this.activeForegroundTurnId !== turnId) {
1959
- traceOpenCode("event.translated.skip-active", { turnId, type: e.type });
1960
- return;
1961
- }
1962
- if (e.type === "timeline" && e.item.type === "tool_call") {
1963
- this.trackToolCall(e.item);
1964
- }
1965
- const terminalEvent = toTerminalTurnEvent(e);
1966
- if (terminalEvent) {
1967
- traceOpenCode("event.terminal", { turnId, type: terminalEvent.type });
1968
- this.finishForegroundTurn(terminalEvent, turnId);
1969
- return;
1970
- }
1971
- this.notifySubscribers(e, turnId);
1907
+ if (!shouldContinue) {
1908
+ return;
1972
1909
  }
1973
1910
  }
1974
- traceOpenCode("stream.eof", {
1911
+ this.traceOpenCode("provider.opencode.stream.eof", {
1975
1912
  turnId,
1976
1913
  eventCount,
1977
1914
  aborted: turnAbortController.signal.aborted,
1978
1915
  stillActive: this.activeForegroundTurnId === turnId,
1979
1916
  });
1980
1917
  if (!turnAbortController.signal.aborted && this.activeForegroundTurnId === turnId) {
1981
- const recovered = await this.recoverTurnFromPersistedCompletion(turnId);
1982
- traceOpenCode("recovery.result", { turnId, recovered });
1983
- if (recovered) {
1984
- return;
1918
+ this.traceOpenCode("provider.opencode.turn.fail_eof", { turnId, eventCount });
1919
+ if (!subscriptionReadyResolved) {
1920
+ subscriptionReady.reject(new Error("OpenCode event stream ended before it became ready"));
1985
1921
  }
1986
- traceOpenCode("turn.fail.eof", { turnId, eventCount });
1987
1922
  this.finishForegroundTurn({
1988
1923
  type: "turn_failed",
1989
1924
  provider: "opencode",
@@ -1992,7 +1927,7 @@ class OpenCodeAgentSession {
1992
1927
  }
1993
1928
  }
1994
1929
  catch (error) {
1995
- traceOpenCode("subscribe.error", {
1930
+ this.traceOpenCode("provider.opencode.subscribe.error", {
1996
1931
  turnId,
1997
1932
  error: error instanceof Error ? { name: error.name, message: error.message } : String(error),
1998
1933
  });
@@ -2018,342 +1953,62 @@ class OpenCodeAgentSession {
2018
1953
  }
2019
1954
  }
2020
1955
  }
2021
- async recoverTurnFromPersistedCompletion(turnId) {
2022
- traceOpenCode("recovery.start", {
1956
+ async consumeOpenCodeStreamEvent(params) {
1957
+ const { rawEvent, eventCount, turnId, turnAbortController } = params;
1958
+ const event = unwrapOpenCodeGlobalEvent(rawEvent);
1959
+ this.traceOpenCode("provider.opencode.raw_event", {
2023
1960
  turnId,
2024
- foregroundTurnStartedAt: this.foregroundTurnStartedAt,
2025
- knownMessageIds: Array.from(this.foregroundKnownMessageIds),
2026
- sessionId: this.sessionId,
2027
- timeoutMs: this.recovery.timeoutMs,
2028
- pollIntervalMs: this.recovery.pollIntervalMs,
1961
+ n: eventCount,
1962
+ type: event?.type,
1963
+ rawType: readOpenCodeRecord(rawEvent)?.type,
1964
+ directory: readOpenCodeRecord(rawEvent)?.directory,
1965
+ rawEvent,
1966
+ properties: event?.properties,
2029
1967
  });
2030
- const startedAt = this.foregroundTurnStartedAt;
2031
- if (startedAt === null) {
2032
- traceOpenCode("recovery.no-start-time", { turnId });
2033
- return false;
2034
- }
2035
- const completionDeadline = Date.now() + this.recovery.timeoutMs;
2036
- const livenessDeadline = Date.now() + this.recovery.livenessMs;
2037
- let attempt = 0;
2038
- let observedActivity = false;
2039
- while (true) {
2040
- if (this.activeForegroundTurnId !== turnId) {
2041
- traceOpenCode("recovery.cancelled", { turnId, attempt });
2042
- return true;
2043
- }
2044
- attempt += 1;
2045
- const emittedPromptIds = await this.pollPendingQuestionsAndPermissions(turnId);
2046
- if (emittedPromptIds > 0) {
2047
- observedActivity = true;
2048
- }
2049
- const outcome = await this.fetchAssistantOutcomeFromMessagesApi(startedAt);
2050
- traceOpenCode("recovery.poll", {
2051
- turnId,
2052
- attempt,
2053
- kind: outcome?.kind ?? "none",
2054
- messageId: outcome?.messageId,
2055
- emittedPromptIds,
2056
- });
2057
- if (outcome?.kind === "failure") {
2058
- this.foregroundKnownMessageIds.add(outcome.messageId);
2059
- this.finishForegroundTurn({
2060
- type: "turn_failed",
2061
- provider: "opencode",
2062
- error: outcome.error,
2063
- }, turnId);
2064
- return true;
2065
- }
2066
- if (outcome?.kind === "completion") {
2067
- this.emitIncrementalAssistantParts(outcome.parts, turnId);
2068
- return this.applyRecoveredAssistantCompletion(outcome, turnId);
2069
- }
2070
- if (outcome?.kind === "in-progress") {
2071
- observedActivity = true;
2072
- this.emitIncrementalAssistantParts(outcome.parts, turnId);
2073
- }
2074
- const now = Date.now();
2075
- if (!observedActivity && now >= livenessDeadline) {
2076
- const deferred = await this.deferForPendingPermissionOrFailRecoveredTurnAfterCap(turnId, attempt, "liveness");
2077
- if (deferred) {
2078
- continue;
2079
- }
2080
- return true;
2081
- }
2082
- if (now >= completionDeadline) {
2083
- const deferred = await this.deferForPendingPermissionOrFailRecoveredTurnAfterCap(turnId, attempt, "completion");
2084
- if (deferred) {
2085
- continue;
2086
- }
2087
- return true;
2088
- }
2089
- const waitMs = Math.min(this.recovery.pollIntervalMs, completionDeadline - now);
2090
- await new Promise((resolve) => setTimeout(resolve, waitMs));
1968
+ if (!event) {
1969
+ return true;
2091
1970
  }
2092
- }
2093
- async deferForPendingPermissionOrFailRecoveredTurnAfterCap(turnId, attempt, cap) {
2094
- if (this.pendingPermissions.size > 0) {
2095
- // A pending OpenCode question/permission means the turn is blocked on
2096
- // user input, not dead. Keep polling until the user response lets the
2097
- // assistant finish or the turn is canceled.
2098
- traceOpenCode(`recovery.${cap}-deferred-for-permission`, {
1971
+ if (turnAbortController.signal.aborted || this.activeForegroundTurnId !== turnId) {
1972
+ this.traceOpenCode("provider.opencode.event.skip", {
2099
1973
  turnId,
2100
- attempt,
2101
- pendingPermissionIds: Array.from(this.pendingPermissions.keys()),
1974
+ n: eventCount,
1975
+ aborted: turnAbortController.signal.aborted,
1976
+ activeTurnId: this.activeForegroundTurnId,
2102
1977
  });
2103
- await new Promise((resolve) => setTimeout(resolve, this.recovery.pollIntervalMs));
2104
- return true;
1978
+ return false;
2105
1979
  }
2106
- traceOpenCode(cap === "liveness" ? "recovery.liveness-exhausted" : "recovery.exhausted", {
1980
+ this.armRetryFailureTimerForStatus(event, turnId);
1981
+ const translated = await this.translateEvent(event);
1982
+ this.traceOpenCode("provider.opencode.parsed_event", {
2107
1983
  turnId,
2108
- attempt,
2109
- });
2110
- await this.failRecoveredTurnAfterCap(turnId, cap);
2111
- return false;
2112
- }
2113
- async failRecoveredTurnAfterCap(turnId, cap) {
2114
- await this.abortOpenCodeSessionAfterRecoveryCap(turnId, cap);
2115
- this.finishForegroundTurn({
2116
- type: "turn_failed",
2117
- provider: "opencode",
2118
- error: "OpenCode event stream ended before the turn reached a terminal state",
2119
- }, turnId);
2120
- }
2121
- async abortOpenCodeSessionAfterRecoveryCap(turnId, cap) {
2122
- const abortPromise = this.beginSessionAbort(turnId, `recovery-${cap}`);
2123
- await withTimeout(abortPromise, OPENCODE_RECOVERY_ABORT_TIMEOUT_MS, "OpenCode session.abort").catch((error) => {
2124
- this.logger.warn({ err: error, sessionId: this.sessionId, turnId, cap }, "OpenCode session.abort exceeded the EOF recovery cap");
1984
+ n: eventCount,
1985
+ count: translated.length,
1986
+ types: translated.map((t) => t.type),
1987
+ events: translated,
2125
1988
  });
2126
- }
2127
- async pollPendingQuestionsAndPermissions(turnId) {
2128
- const [questionsResponse, permissionsResponse] = await Promise.all([
2129
- Promise.resolve()
2130
- .then(() => this.client.question.list({ directory: this.config.cwd }))
2131
- .catch((error) => {
2132
- traceOpenCode("recovery.question-list.throw", {
2133
- turnId,
2134
- error: error instanceof Error
2135
- ? { name: error.name, message: error.message, stack: error.stack }
2136
- : String(error),
2137
- });
2138
- return null;
2139
- }),
2140
- Promise.resolve()
2141
- .then(() => this.client.permission.list({ directory: this.config.cwd }))
2142
- .catch((error) => {
2143
- traceOpenCode("recovery.permission-list.throw", {
2144
- turnId,
2145
- error: error instanceof Error
2146
- ? { name: error.name, message: error.message, stack: error.stack }
2147
- : String(error),
2148
- });
2149
- return null;
2150
- }),
2151
- ]);
2152
- if (this.activeForegroundTurnId !== turnId)
2153
- return 0;
2154
- let emitted = 0;
2155
- for (const question of questionsResponse?.data ?? []) {
2156
- if (question.sessionID !== this.sessionId)
2157
- continue;
2158
- if (this.foregroundEmittedQuestionIds.has(question.id))
2159
- continue;
2160
- this.foregroundEmittedQuestionIds.add(question.id);
2161
- emitted += 1;
2162
- const synthetic = {
2163
- id: question.id,
2164
- type: "question.asked",
2165
- properties: question,
2166
- };
2167
- const events = await this.translateEvent(synthetic);
2168
- for (const event of events) {
2169
- this.notifySubscribers(event, turnId);
2170
- }
2171
- }
2172
- for (const permission of permissionsResponse?.data ?? []) {
2173
- if (permission.sessionID !== this.sessionId)
2174
- continue;
2175
- if (this.foregroundEmittedPermissionIds.has(permission.id))
2176
- continue;
2177
- this.foregroundEmittedPermissionIds.add(permission.id);
2178
- emitted += 1;
2179
- const synthetic = {
2180
- id: permission.id,
2181
- type: "permission.asked",
2182
- properties: permission,
2183
- };
2184
- const events = await this.translateEvent(synthetic);
2185
- for (const event of events) {
2186
- this.notifySubscribers(event, turnId);
2187
- }
2188
- }
2189
- return emitted;
2190
- }
2191
- async fetchAssistantOutcomeFromMessagesApi(startedAt) {
2192
- const response = await Promise.resolve()
2193
- .then(() => this.client.session.messages({
2194
- sessionID: this.sessionId,
2195
- directory: this.config.cwd,
2196
- }))
2197
- .catch((error) => {
2198
- traceOpenCode("recovery.messages.throw", {
2199
- error: error instanceof Error
2200
- ? { name: error.name, message: error.message, stack: error.stack }
2201
- : String(error),
2202
- });
2203
- return null;
2204
- });
2205
- if (response === null) {
2206
- return null;
2207
- }
2208
- if (response.error || !response.data) {
2209
- return null;
2210
- }
2211
- for (let index = response.data.length - 1; index >= 0; index -= 1) {
2212
- const item = response.data[index];
2213
- if (!item)
2214
- continue;
2215
- const info = item.info;
2216
- if (info.role !== "assistant")
2217
- continue;
2218
- if (this.foregroundKnownMessageIds.has(info.id))
2219
- continue;
2220
- if (typeof info.time?.created === "number" && info.time.created < startedAt)
2221
- continue;
2222
- if (info.error) {
2223
- return {
2224
- kind: "failure",
2225
- messageId: info.id,
2226
- error: formatOpenCodeAssistantErrorMessage(info.error),
2227
- };
1989
+ for (const e of translated) {
1990
+ if (this.activeForegroundTurnId !== turnId) {
1991
+ this.traceOpenCode("provider.opencode.parsed_event.skip_active", { turnId, type: e.type });
1992
+ return false;
2228
1993
  }
2229
- if (typeof info.time?.completed !== "number") {
2230
- return { kind: "in-progress", messageId: info.id, parts: item.parts };
1994
+ if (e.type === "timeline" && e.item.type === "tool_call") {
1995
+ this.trackToolCall(e.item);
2231
1996
  }
2232
- let text = item.parts
2233
- .filter((part) => part.type === "text")
2234
- .map((part) => (part.text ?? "").trim())
2235
- .filter((part) => part.length > 0)
2236
- .join("\n\n");
2237
- if (!text) {
2238
- text = stringifyStructuredAssistantMessage(info.structured) ?? "";
2239
- }
2240
- if (!text)
2241
- continue;
2242
- const usage = {};
2243
- mergeOpenCodeStepFinishUsage(usage, { cost: info.cost, tokens: info.tokens });
2244
- return { kind: "completion", messageId: info.id, text, parts: item.parts, usage };
2245
- }
2246
- return null;
2247
- }
2248
- emitIncrementalAssistantParts(parts, turnId) {
2249
- for (const part of parts) {
2250
- if (part.type === "reasoning" && part.text) {
2251
- const emittedTextLength = this.foregroundEmittedReasoningTextLengthByPartId.get(part.id) ?? 0;
2252
- if (part.text.length <= emittedTextLength)
2253
- continue;
2254
- const text = part.text.slice(emittedTextLength);
2255
- this.foregroundEmittedReasoningTextLengthByPartId.set(part.id, part.text.length);
2256
- this.notifySubscribers({
2257
- type: "timeline",
2258
- provider: "opencode",
2259
- item: { type: "reasoning", text },
2260
- }, turnId);
2261
- continue;
1997
+ const terminalEvent = toTerminalTurnEvent(e);
1998
+ if (terminalEvent) {
1999
+ this.traceOpenCode("provider.opencode.event.terminal", {
2000
+ turnId,
2001
+ type: terminalEvent.type,
2002
+ });
2003
+ this.finishForegroundTurn(terminalEvent, turnId);
2004
+ return false;
2262
2005
  }
2263
- if (part.type !== "tool")
2264
- continue;
2265
- const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
2266
- if (!parsedToolPart.success || !parsedToolPart.data)
2267
- continue;
2268
- const callId = parsedToolPart.data.callId;
2269
- const signature = this.createRecoveredToolCallSignature(part, parsedToolPart.data);
2270
- const lastSignature = this.foregroundEmittedToolCallSignatureByCallId.get(callId);
2271
- if (lastSignature === signature)
2272
- continue;
2273
- this.foregroundEmittedToolCallSignatureByCallId.set(callId, signature);
2274
- this.trackToolCall(parsedToolPart.data);
2275
- this.notifySubscribers({
2276
- type: "timeline",
2277
- provider: "opencode",
2278
- item: parsedToolPart.data,
2279
- }, turnId);
2280
- }
2281
- }
2282
- createRecoveredToolCallSignature(part, item) {
2283
- const state = part
2284
- .state;
2285
- return JSON.stringify([
2286
- item.callId,
2287
- item.status,
2288
- state?.input ?? null,
2289
- state?.output ?? null,
2290
- state?.error ?? null,
2291
- ]);
2292
- }
2293
- applyRecoveredAssistantCompletion(completion, turnId) {
2294
- if (this.activeForegroundTurnId !== turnId) {
2295
- return false;
2296
- }
2297
- this.foregroundKnownMessageIds.add(completion.messageId);
2298
- this.logger.warn({ sessionId: this.sessionId, turnId }, "Recovered OpenCode turn completion via messages API after SSE EOF");
2299
- const recoveryText = this.resolvePersistedAssistantRecoveryText(completion.text);
2300
- if (recoveryText === null) {
2301
- return false;
2006
+ this.notifySubscribers(e, turnId);
2302
2007
  }
2303
- if (recoveryText.length > 0) {
2304
- this.notifySubscribers({
2305
- type: "timeline",
2306
- provider: "opencode",
2307
- item: { type: "assistant_message", text: recoveryText },
2308
- }, turnId);
2309
- this.foregroundAssistantMessageEmitted = true;
2310
- }
2311
- if (hasNormalizedOpenCodeUsage(completion.usage) && !this.foregroundUsageUpdated) {
2312
- this.accumulatedUsage = {
2313
- ...this.accumulatedUsage,
2314
- ...completion.usage,
2315
- };
2316
- this.notifySubscribers({
2317
- type: "usage_updated",
2318
- provider: "opencode",
2319
- usage: { ...this.accumulatedUsage },
2320
- }, turnId);
2321
- this.foregroundUsageUpdated = true;
2322
- }
2323
- this.finishForegroundTurn({
2324
- type: "turn_completed",
2325
- provider: "opencode",
2326
- usage: hasNormalizedOpenCodeUsage(this.accumulatedUsage)
2327
- ? { ...this.accumulatedUsage }
2328
- : undefined,
2329
- }, turnId);
2330
2008
  return true;
2331
2009
  }
2332
- resolvePersistedAssistantRecoveryText(completedText) {
2333
- if (!this.foregroundAssistantMessageEmitted) {
2334
- return completedText;
2335
- }
2336
- if (completedText === this.foregroundAssistantText) {
2337
- return "";
2338
- }
2339
- return completedText.startsWith(this.foregroundAssistantText)
2340
- ? completedText.slice(this.foregroundAssistantText.length)
2341
- : null;
2342
- }
2343
- async readPersistedSessionMessageIds() {
2344
- const messageRoot = path.join(this.storageRoot, "message", this.sessionId);
2345
- const messageFiles = await findJsonFiles(messageRoot);
2346
- const messageIds = new Set();
2347
- for (const file of messageFiles) {
2348
- const parsed = await readJsonFile(file, OpenCodeStoredMessageSchema);
2349
- if (parsed?.sessionID === this.sessionId) {
2350
- messageIds.add(parsed.id);
2351
- }
2352
- }
2353
- return messageIds;
2354
- }
2355
2010
  finishForegroundTurn(event, turnId) {
2356
- traceOpenCode("finishForegroundTurn", {
2011
+ this.traceOpenCode("provider.opencode.finish_foreground_turn", {
2357
2012
  turnId,
2358
2013
  activeTurnId: this.activeForegroundTurnId,
2359
2014
  type: event.type,
@@ -2369,7 +2024,7 @@ class OpenCodeAgentSession {
2369
2024
  else {
2370
2025
  this.runningToolCalls.clear();
2371
2026
  }
2372
- this.foregroundTurnStartedAt = null;
2027
+ this.clearRetryFailureTimer();
2373
2028
  this.activeForegroundTurnId = null;
2374
2029
  // Abort the SSE connection so the SDK tears down the underlying fetch.
2375
2030
  this.abortController?.abort();
@@ -2383,6 +2038,37 @@ class OpenCodeAgentSession {
2383
2038
  }
2384
2039
  this.runningToolCalls.delete(item.callId);
2385
2040
  }
2041
+ armRetryFailureTimerForStatus(event, turnId) {
2042
+ if (this.retryFailureTimer || event.type !== "session.status") {
2043
+ return;
2044
+ }
2045
+ if (event.properties.sessionID !== this.sessionId || event.properties.status.type !== "retry") {
2046
+ return;
2047
+ }
2048
+ const retry = event.properties.status;
2049
+ const message = typeof retry.message === "string" ? retry.message.trim() : "";
2050
+ const error = message
2051
+ ? `OpenCode provider retry did not recover: ${message}`
2052
+ : "OpenCode provider retry did not recover";
2053
+ this.retryFailureTimer = setTimeout(() => {
2054
+ this.retryFailureTimer = null;
2055
+ if (this.activeForegroundTurnId !== turnId) {
2056
+ return;
2057
+ }
2058
+ this.finishForegroundTurn({
2059
+ type: "turn_failed",
2060
+ provider: "opencode",
2061
+ error,
2062
+ }, turnId);
2063
+ }, OPENCODE_RETRY_STATUS_FAILURE_MS);
2064
+ }
2065
+ clearRetryFailureTimer() {
2066
+ if (!this.retryFailureTimer) {
2067
+ return;
2068
+ }
2069
+ clearTimeout(this.retryFailureTimer);
2070
+ this.retryFailureTimer = null;
2071
+ }
2386
2072
  synthesizeInterruptedToolCalls(turnId) {
2387
2073
  for (const item of this.runningToolCalls.values()) {
2388
2074
  const error = { message: "Tool execution aborted" };
@@ -2408,14 +2094,11 @@ class OpenCodeAgentSession {
2408
2094
  }
2409
2095
  notifySubscribers(event, turnIdOverride) {
2410
2096
  const turnId = turnIdOverride ?? this.activeForegroundTurnId;
2411
- if (event.type === "timeline" && event.item.type === "assistant_message") {
2412
- this.foregroundAssistantMessageEmitted = true;
2413
- this.foregroundAssistantText += event.item.text;
2414
- }
2415
- if (event.type === "usage_updated") {
2416
- this.foregroundUsageUpdated = true;
2417
- }
2418
2097
  const tagged = turnId ? { ...event, turnId } : event;
2098
+ this.traceOpenCode("provider.opencode.event_emit", {
2099
+ turnId: getAgentStreamEventTurnId(tagged),
2100
+ event: tagged,
2101
+ });
2419
2102
  for (const callback of this.subscribers) {
2420
2103
  try {
2421
2104
  callback(tagged);
@@ -2428,6 +2111,15 @@ class OpenCodeAgentSession {
2428
2111
  createTurnId() {
2429
2112
  return `opencode-turn-${this.nextTurnOrdinal++}`;
2430
2113
  }
2114
+ traceOpenCode(msg, data = {}) {
2115
+ this.logger.trace({
2116
+ agentId: this.agentId,
2117
+ provider: "opencode",
2118
+ sessionId: this.sessionId,
2119
+ turnId: data.turnId ?? this.activeForegroundTurnId ?? undefined,
2120
+ ...data,
2121
+ }, msg);
2122
+ }
2431
2123
  async *streamHistory() {
2432
2124
  const response = await this.client.session.messages({
2433
2125
  sessionID: this.sessionId,