@opengeni/codex 0.2.15 → 0.2.17

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/index.js CHANGED
@@ -821,6 +821,12 @@ function opaqueProviderArtifactFingerprints(input) {
821
821
  }
822
822
 
823
823
  // src/fetch.ts
824
+ function emitRequestPreparationDiagnostic(ctx, phase) {
825
+ try {
826
+ ctx.onRequestPreparationDiagnostic?.(phase);
827
+ } catch {
828
+ }
829
+ }
824
830
  var CODEX_TRANSPORT_ERROR_HEADER = "x-opengeni-codex-transport-error";
825
831
  var CODEX_REQUEST_BODY_NORMALIZED_HEADER = "x-opengeni-request-body-normalized";
826
832
  var REPLAYABLE_REQUEST_BODY_FACTORY = Symbol.for("opengeni.replayable-request-body-factory");
@@ -914,15 +920,90 @@ function parseCodexUsageHeaders(headers) {
914
920
  checkedAt: new Date(nowMs)
915
921
  };
916
922
  }
917
- async function emitRequestEvent(audit, event) {
918
- await audit.ctx.onModelRequestEvent?.({
923
+ function classifyCodexSseTerminal(ev) {
924
+ if (ev.type === "response.failed") {
925
+ return {
926
+ phase: "failed",
927
+ rawError: ev.response?.error,
928
+ fallbackCode: "response_failed",
929
+ fallbackMessage: "The Codex response failed"
930
+ };
931
+ }
932
+ if (ev.type === "error" || ev.type === "response.error") {
933
+ return {
934
+ phase: "failed",
935
+ rawError: ev.error ?? ev.response?.error ?? ev,
936
+ fallbackCode: "response_error",
937
+ fallbackMessage: "The Codex response stream reported an error"
938
+ };
939
+ }
940
+ if (ev.type === "response.incomplete") {
941
+ const details = ev.response?.incomplete_details;
942
+ const reason = details && typeof details === "object" ? details.reason : void 0;
943
+ return {
944
+ phase: "failed",
945
+ rawError: {
946
+ code: "response_incomplete",
947
+ message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
948
+ },
949
+ fallbackCode: "response_incomplete",
950
+ fallbackMessage: "The Codex response was incomplete"
951
+ };
952
+ }
953
+ if (ev.type !== "response.completed" && ev.type !== "response.done") {
954
+ return null;
955
+ }
956
+ if (!ev.response) {
957
+ return null;
958
+ }
959
+ const responseStatus = ev.response.status;
960
+ if (responseStatus !== void 0 && responseStatus !== "completed" || ev.response.error !== null && ev.response.error !== void 0) {
961
+ const incomplete = responseStatus === "incomplete";
962
+ return {
963
+ phase: "failed",
964
+ rawError: ev.response.error,
965
+ fallbackCode: incomplete ? "response_incomplete" : "response_failed",
966
+ fallbackMessage: incomplete ? "The Codex response was incomplete" : "The Codex response failed"
967
+ };
968
+ }
969
+ return { phase: "completed" };
970
+ }
971
+ function markSemanticTerminal(state, phase) {
972
+ if (state.phase === null) {
973
+ state.phase = phase;
974
+ }
975
+ }
976
+ function terminalOutcomeForPhase(phase) {
977
+ if (phase === "completed" || phase === "failed" || phase === "timed_out") {
978
+ return phase;
979
+ }
980
+ return null;
981
+ }
982
+ function requestEventFor(audit, event) {
983
+ return {
919
984
  requestId: audit.requestId,
920
985
  transportAttempt: audit.transportAttempt,
921
986
  ...audit.model ? { model: audit.model } : {},
922
- durationMs: Math.max(0, Date.now() - audit.attemptStartedAt),
987
+ durationMs: Math.max(0, performance.now() - audit.attemptStartedAtMonotonic),
923
988
  timeoutPolicy: audit.policy,
924
989
  ...event
925
- });
990
+ };
991
+ }
992
+ async function emitRequestEvent(audit, event) {
993
+ const terminalOutcome = terminalOutcomeForPhase(event.phase);
994
+ if (terminalOutcome !== null) {
995
+ if (audit.terminalOutcome !== null) {
996
+ return false;
997
+ }
998
+ audit.terminalOutcome = terminalOutcome;
999
+ }
1000
+ const observed = requestEventFor(audit, event);
1001
+ try {
1002
+ audit.ctx.onModelRequestDiagnostic?.(observed);
1003
+ } catch {
1004
+ }
1005
+ await audit.ctx.onModelRequestEvent?.(observed);
1006
+ return true;
926
1007
  }
927
1008
  function providerRequestId(headers) {
928
1009
  return headers.get("x-request-id") ?? headers.get("request-id") ?? void 0;
@@ -963,11 +1044,12 @@ async function fetchBeforeHeaders(base, input, init, audit) {
963
1044
  externalSignal?.removeEventListener("abort", forwardAbort);
964
1045
  }
965
1046
  }
966
- async function observedResponse(res, audit, externalSignal) {
1047
+ async function observedResponse(res, audit, externalSignal, semanticTerminal) {
967
1048
  const requestId = providerRequestId(res.headers);
968
1049
  if (!res.body) {
1050
+ if (semanticTerminal) markSemanticTerminal(semanticTerminal, "failed");
969
1051
  await emitRequestEvent(audit, {
970
- phase: res.ok ? "completed" : "failed",
1052
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
971
1053
  responseObserved: true,
972
1054
  status: res.status,
973
1055
  ...requestId ? { providerRequestId: requestId } : {}
@@ -992,17 +1074,19 @@ async function observedResponse(res, audit, externalSignal) {
992
1074
  if (terminal) return;
993
1075
  terminal = true;
994
1076
  clearTimers();
1077
+ const semanticPhase = semanticTerminal?.phase;
1078
+ const phase = semanticPhase ?? "timed_out";
995
1079
  const error = new CodexResponseTimeoutError(klass, audit.requestId, true);
996
1080
  void reader.cancel(error).catch(() => void 0);
997
1081
  void emitRequestEvent(audit, {
998
- phase: "timed_out",
1082
+ phase,
999
1083
  responseObserved: true,
1000
- timeoutClass: klass,
1084
+ ...phase === "timed_out" ? { timeoutClass: klass } : {},
1001
1085
  status: res.status,
1002
1086
  ...requestId ? { providerRequestId: requestId } : {}
1003
1087
  }).then(
1004
- () => controller.error(error),
1005
- () => controller.error(error)
1088
+ () => phase === "completed" ? controller.close() : controller.error(error),
1089
+ () => phase === "completed" ? controller.close() : controller.error(error)
1006
1090
  );
1007
1091
  };
1008
1092
  armIdle = () => {
@@ -1022,13 +1106,13 @@ async function observedResponse(res, audit, externalSignal) {
1022
1106
  const reason = externalSignal?.reason ?? new DOMException("Aborted", "AbortError");
1023
1107
  void reader.cancel(reason).catch(() => void 0);
1024
1108
  void emitRequestEvent(audit, {
1025
- phase: "failed",
1109
+ phase: semanticTerminal?.phase ?? "failed",
1026
1110
  responseObserved: true,
1027
1111
  status: res.status,
1028
1112
  ...requestId ? { providerRequestId: requestId } : {}
1029
1113
  }).then(
1030
- () => controller.error(reason),
1031
- () => controller.error(reason)
1114
+ () => semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason),
1115
+ () => semanticTerminal?.phase === "completed" ? controller.close() : controller.error(reason)
1032
1116
  );
1033
1117
  };
1034
1118
  if (externalSignal?.aborted) {
@@ -1047,12 +1131,19 @@ async function observedResponse(res, audit, externalSignal) {
1047
1131
  if (chunk.done) {
1048
1132
  terminal = true;
1049
1133
  clearTimers();
1050
- await emitRequestEvent(audit, {
1051
- phase: res.ok ? "completed" : "failed",
1052
- responseObserved: true,
1053
- status: res.status,
1054
- ...requestId ? { providerRequestId: requestId } : {}
1055
- });
1134
+ if (semanticTerminal && semanticTerminal.phase === null) {
1135
+ if (!semanticTerminal.deferTransportTerminal) {
1136
+ markSemanticTerminal(semanticTerminal, "failed");
1137
+ }
1138
+ }
1139
+ if (!semanticTerminal?.deferTransportTerminal || semanticTerminal.phase !== null) {
1140
+ await emitRequestEvent(audit, {
1141
+ phase: semanticTerminal?.phase ?? (res.ok ? "completed" : "failed"),
1142
+ responseObserved: true,
1143
+ status: res.status,
1144
+ ...requestId ? { providerRequestId: requestId } : {}
1145
+ });
1146
+ }
1056
1147
  controller.close();
1057
1148
  return;
1058
1149
  }
@@ -1075,21 +1166,32 @@ async function observedResponse(res, audit, externalSignal) {
1075
1166
  if (terminal) return;
1076
1167
  terminal = true;
1077
1168
  clearTimers();
1169
+ const semanticPhase = semanticTerminal?.phase;
1170
+ if (semanticTerminal && semanticPhase === null) {
1171
+ markSemanticTerminal(semanticTerminal, "failed");
1172
+ }
1078
1173
  await emitRequestEvent(audit, {
1079
- phase: "failed",
1174
+ phase: semanticPhase ?? "failed",
1080
1175
  responseObserved: true,
1081
1176
  status: res.status,
1082
1177
  ...requestId ? { providerRequestId: requestId } : {}
1083
1178
  });
1084
- controller.error(error);
1179
+ if (semanticPhase === "completed") {
1180
+ controller.close();
1181
+ } else {
1182
+ controller.error(error);
1183
+ }
1085
1184
  }
1086
1185
  },
1087
1186
  async cancel(reason) {
1088
1187
  if (!terminal) {
1089
1188
  terminal = true;
1090
1189
  clearTimers();
1190
+ if (semanticTerminal && semanticTerminal.phase === null) {
1191
+ markSemanticTerminal(semanticTerminal, "failed");
1192
+ }
1091
1193
  await emitRequestEvent(audit, {
1092
- phase: "failed",
1194
+ phase: semanticTerminal?.phase ?? "failed",
1093
1195
  responseObserved: true,
1094
1196
  status: res.status,
1095
1197
  ...requestId ? { providerRequestId: requestId } : {}
@@ -1134,6 +1236,7 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1134
1236
  if (!ctx) {
1135
1237
  return base(input, init);
1136
1238
  }
1239
+ emitRequestPreparationDiagnostic(ctx, "transport_entry");
1137
1240
  const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1138
1241
  const rewritten = rawUrl.replace(/(?<!\/codex)\/responses(\b|$)/, "/codex/responses$1");
1139
1242
  const policy = resolveCodexResponseTimeoutPolicy(ctx.responseTimeoutPolicy);
@@ -1197,7 +1300,10 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1197
1300
  throw new Error("Model request could not be prepared");
1198
1301
  }
1199
1302
  if (!bodyAlreadyNormalized) {
1200
- ctx.onRequestOpaqueArtifacts?.({ requestId, fingerprints: requestOpaqueArtifacts });
1303
+ ctx.onRequestOpaqueArtifacts?.({
1304
+ requestId,
1305
+ fingerprints: requestOpaqueArtifacts
1306
+ });
1201
1307
  }
1202
1308
  headers.set(
1203
1309
  "Idempotency-Key",
@@ -1219,13 +1325,19 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1219
1325
  transportAttempt,
1220
1326
  ...model ? { model } : {},
1221
1327
  logicalStartedAt,
1222
- attemptStartedAt: Date.now(),
1223
- policy
1328
+ attemptStartedAtMonotonic: performance.now(),
1329
+ policy,
1330
+ terminalOutcome: null
1224
1331
  };
1332
+ emitRequestPreparationDiagnostic(ctx, "wire_request_ready");
1225
1333
  await emitRequestEvent(audit, {
1226
1334
  phase: "started",
1227
1335
  responseObserved: false
1228
1336
  });
1337
+ const semanticTerminal = {
1338
+ phase: null,
1339
+ deferTransportTerminal: !callerWantsStream
1340
+ };
1229
1341
  try {
1230
1342
  res = await fetchBeforeHeaders(base, rewritten, nextInit, audit);
1231
1343
  const upstreamRequestId = providerRequestId(res.headers);
@@ -1235,8 +1347,7 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1235
1347
  status: res.status,
1236
1348
  ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1237
1349
  });
1238
- const observed = await observedResponse(res, audit, nextInit.signal);
1239
- res = observed;
1350
+ res = await observedResponse(res, audit, nextInit.signal, semanticTerminal);
1240
1351
  } catch (error) {
1241
1352
  if (nextInit.signal?.aborted) {
1242
1353
  await emitRequestEvent(audit, {
@@ -1273,12 +1384,30 @@ function codexSubscriptionFetch(base = globalThis.fetch) {
1273
1384
  });
1274
1385
  }
1275
1386
  if (!res.ok) {
1276
- return await bufferCodexErrorResponse(res);
1387
+ const buffered = await bufferCodexErrorResponse(res);
1388
+ const upstreamRequestId = providerRequestId(res.headers);
1389
+ markSemanticTerminal(semanticTerminal, "failed");
1390
+ await emitRequestEvent(audit, {
1391
+ phase: "failed",
1392
+ responseObserved: true,
1393
+ status: res.status,
1394
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1395
+ }).catch(() => void 0);
1396
+ return buffered;
1277
1397
  }
1278
- return callerWantsStream ? validateCodexStream(res) : await sseToJsonResponse(res);
1398
+ if (callerWantsStream) {
1399
+ res = validateCodexStream(res, (phase) => {
1400
+ markSemanticTerminal(semanticTerminal, phase);
1401
+ });
1402
+ } else {
1403
+ res = await sseToJsonResponse(res, audit, semanticTerminal);
1404
+ }
1405
+ return res;
1279
1406
  };
1280
1407
  try {
1281
- let res = await attempt(await ctx.getToken(), 0);
1408
+ const token = await ctx.getToken();
1409
+ emitRequestPreparationDiagnostic(ctx, "credential_ready");
1410
+ let res = await attempt(token, 0);
1282
1411
  if (res.status === 401) {
1283
1412
  res = await attempt(await ctx.refresh(), 1);
1284
1413
  }
@@ -1382,7 +1511,8 @@ async function readBoundedResponseText(response, maxBytes) {
1382
1511
  }
1383
1512
  return { text: parts.join(""), truncated };
1384
1513
  }
1385
- async function sseToJsonResponse(res) {
1514
+ async function sseToJsonResponse(res, audit, semanticTerminal) {
1515
+ const upstreamRequestId = providerRequestId(res.headers);
1386
1516
  const text = await res.text();
1387
1517
  let final = null;
1388
1518
  let terminalError = null;
@@ -1395,57 +1525,45 @@ async function sseToJsonResponse(res) {
1395
1525
  const ev = JSON.parse(data);
1396
1526
  if (ev.type === "response.output_item.done" && ev.item !== void 0) {
1397
1527
  items.push(ev.item);
1398
- } else if (ev.type === "response.failed") {
1399
- terminalError = codexSseFailureResponse(
1400
- res,
1401
- ev.response?.error,
1402
- "response_failed",
1403
- "The Codex response failed",
1404
- {
1405
- eventType: ev.type,
1406
- responseId: ev.response?.id,
1407
- responseStatus: ev.response?.status
1408
- }
1409
- );
1410
- } else if (ev.type === "error" || ev.type === "response.error") {
1411
- terminalError = codexSseFailureResponse(
1412
- res,
1413
- ev.error ?? ev.response?.error ?? ev,
1414
- "response_error",
1415
- "The Codex response stream reported an error",
1416
- {
1417
- eventType: ev.type,
1418
- responseId: ev.response?.id,
1419
- responseStatus: ev.response?.status
1420
- }
1421
- );
1422
- } else if (ev.type === "response.incomplete") {
1423
- const details = ev.response?.incomplete_details;
1424
- const reason = details && typeof details === "object" ? details.reason : void 0;
1425
- terminalError = codexSseFailureResponse(
1426
- res,
1427
- {
1428
- code: "response_incomplete",
1429
- message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
1430
- },
1431
- "response_incomplete",
1432
- "The Codex response was incomplete",
1433
- {
1434
- eventType: ev.type,
1435
- responseId: ev.response?.id,
1436
- responseStatus: ev.response?.status
1437
- }
1438
- );
1439
- } else if (ev.type === "response.completed" || ev.type === "response.done") {
1440
- final = ev.response ?? null;
1528
+ } else {
1529
+ const terminal = classifyCodexSseTerminal(ev);
1530
+ if (terminal?.phase === "failed") {
1531
+ terminalError = codexSseFailureResponse(
1532
+ res,
1533
+ terminal.rawError,
1534
+ terminal.fallbackCode,
1535
+ terminal.fallbackMessage,
1536
+ {
1537
+ eventType: ev.type,
1538
+ responseId: ev.response?.id,
1539
+ responseStatus: ev.response?.status
1540
+ }
1541
+ );
1542
+ } else if (terminal?.phase === "completed") {
1543
+ final = ev.response ?? null;
1544
+ }
1441
1545
  }
1442
1546
  } catch {
1443
1547
  }
1444
1548
  }
1445
1549
  if (terminalError) {
1550
+ markSemanticTerminal(semanticTerminal, "failed");
1551
+ await emitRequestEvent(audit, {
1552
+ phase: "failed",
1553
+ responseObserved: true,
1554
+ status: res.status,
1555
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1556
+ });
1446
1557
  return terminalError;
1447
1558
  }
1448
1559
  if (!final) {
1560
+ markSemanticTerminal(semanticTerminal, "failed");
1561
+ await emitRequestEvent(audit, {
1562
+ phase: "failed",
1563
+ responseObserved: true,
1564
+ status: res.status,
1565
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1566
+ });
1449
1567
  return codexSseFailureResponse(
1450
1568
  res,
1451
1569
  null,
@@ -1461,6 +1579,13 @@ async function sseToJsonResponse(res) {
1461
1579
  `[codex-debug] sse->json items=${items.length} outputLen=${Array.isArray(final?.output) ? final.output.length : "?"}`
1462
1580
  );
1463
1581
  }
1582
+ markSemanticTerminal(semanticTerminal, "completed");
1583
+ await emitRequestEvent(audit, {
1584
+ phase: "completed",
1585
+ responseObserved: true,
1586
+ status: res.status,
1587
+ ...upstreamRequestId ? { providerRequestId: upstreamRequestId } : {}
1588
+ });
1464
1589
  const headers = new Headers(res.headers);
1465
1590
  headers.set("content-type", "application/json");
1466
1591
  headers.delete("content-length");
@@ -1613,8 +1738,9 @@ function codexSseFailureError(source, rawError, fallbackCode, publicMessage, met
1613
1738
  codexSseFailureProjection(source, rawError, fallbackCode, publicMessage, metadata)
1614
1739
  );
1615
1740
  }
1616
- function validateCodexStream(res) {
1741
+ function validateCodexStream(res, onSemanticTerminal) {
1617
1742
  if (!res.body) {
1743
+ onSemanticTerminal?.("failed");
1618
1744
  const error = codexSseFailureError(
1619
1745
  res,
1620
1746
  null,
@@ -1644,7 +1770,7 @@ function validateCodexStream(res) {
1644
1770
  const block = buffer.slice(0, boundary.start);
1645
1771
  const separator = buffer.slice(boundary.start, boundary.end);
1646
1772
  buffer = buffer.slice(boundary.end);
1647
- successfulTerminalSeen ||= inspectCodexSseBlock(block, res);
1773
+ successfulTerminalSeen ||= inspectCodexSseBlock(block, res, onSemanticTerminal);
1648
1774
  controller.enqueue(encoder.encode(`${block}${separator}`));
1649
1775
  boundary = findSseBlockBoundary(buffer, final);
1650
1776
  }
@@ -1658,7 +1784,7 @@ function validateCodexStream(res) {
1658
1784
  buffer += decoder.decode();
1659
1785
  emitCompleteBlocks(controller, true);
1660
1786
  if (buffer.length > 0) {
1661
- successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res);
1787
+ successfulTerminalSeen ||= inspectCodexSseBlock(buffer, res, onSemanticTerminal);
1662
1788
  controller.enqueue(encoder.encode(buffer));
1663
1789
  buffer = "";
1664
1790
  }
@@ -1708,7 +1834,7 @@ var CODEX_TERMINAL_TYPE_HINTS = [
1708
1834
  '"response.error"',
1709
1835
  '"error"'
1710
1836
  ];
1711
- function inspectCodexSseBlock(block, source) {
1837
+ function inspectCodexSseBlock(block, source, onSemanticTerminal) {
1712
1838
  const lines = block.split(/\r\n|\r|\n/);
1713
1839
  const dataStr = lines.filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join("\n");
1714
1840
  if (!dataStr || dataStr === "[DONE]") {
@@ -1723,43 +1849,14 @@ function inspectCodexSseBlock(block, source) {
1723
1849
  } catch {
1724
1850
  return false;
1725
1851
  }
1726
- if (ev.type === "response.failed") {
1727
- throw codexSseFailureError(
1728
- source,
1729
- ev.response?.error,
1730
- "response_failed",
1731
- "The Codex response failed",
1732
- {
1733
- eventType: ev.type,
1734
- responseId: ev.response?.id,
1735
- responseStatus: ev.response?.status
1736
- }
1737
- );
1738
- }
1739
- if (ev.type === "error" || ev.type === "response.error") {
1740
- throw codexSseFailureError(
1741
- source,
1742
- ev.error ?? ev.response?.error ?? ev,
1743
- "response_error",
1744
- "The Codex response stream reported an error",
1745
- {
1746
- eventType: ev.type,
1747
- responseId: ev.response?.id,
1748
- responseStatus: ev.response?.status
1749
- }
1750
- );
1751
- }
1752
- if (ev.type === "response.incomplete") {
1753
- const details = ev.response?.incomplete_details;
1754
- const reason = details && typeof details === "object" ? details.reason : void 0;
1852
+ const terminal = classifyCodexSseTerminal(ev);
1853
+ if (terminal?.phase === "failed") {
1854
+ onSemanticTerminal?.("failed");
1755
1855
  throw codexSseFailureError(
1756
1856
  source,
1757
- {
1758
- code: "response_incomplete",
1759
- message: typeof reason === "string" && reason.length > 0 ? `The Codex response was incomplete (${reason})` : "The Codex response was incomplete"
1760
- },
1761
- "response_incomplete",
1762
- "The Codex response was incomplete",
1857
+ terminal.rawError,
1858
+ terminal.fallbackCode,
1859
+ terminal.fallbackMessage,
1763
1860
  {
1764
1861
  eventType: ev.type,
1765
1862
  responseId: ev.response?.id,
@@ -1767,20 +1864,8 @@ function inspectCodexSseBlock(block, source) {
1767
1864
  }
1768
1865
  );
1769
1866
  }
1770
- if ((ev.type === "response.completed" || ev.type === "response.done") && ev.response) {
1771
- if (ev.response.status !== void 0 && ev.response.status !== "completed" || ev.response.error !== null && ev.response.error !== void 0) {
1772
- throw codexSseFailureError(
1773
- source,
1774
- ev.response.error,
1775
- ev.response.status === "incomplete" ? "response_incomplete" : "response_failed",
1776
- ev.response.status === "incomplete" ? "The Codex response was incomplete" : "The Codex response failed",
1777
- {
1778
- eventType: ev.type,
1779
- responseId: ev.response.id,
1780
- responseStatus: ev.response.status
1781
- }
1782
- );
1783
- }
1867
+ if (terminal?.phase === "completed") {
1868
+ onSemanticTerminal?.("completed");
1784
1869
  return true;
1785
1870
  }
1786
1871
  return false;