@ai-sdk/harness-codex 1.0.42 → 1.0.43
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/CHANGELOG.md +10 -0
- package/dist/bridge/index.mjs +201 -192
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/create-emit-stream-event.ts +287 -0
- package/src/bridge/index.ts +13 -292
- package/src/codex-harness.ts +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ai-sdk/harness-codex
|
|
2
2
|
|
|
3
|
+
## 1.0.43
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 07c977a: chore(harness): refactor bridge code to break out stream event emission from launcher to make it testable
|
|
8
|
+
- 560db3a: fix(harness-codex): fix obsolete default Codex model ID
|
|
9
|
+
- Updated dependencies [a94425b]
|
|
10
|
+
- Updated dependencies [2de0611]
|
|
11
|
+
- @ai-sdk/harness@1.0.41
|
|
12
|
+
|
|
3
13
|
## 1.0.42
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -485,7 +485,6 @@ function serialiseError(err) {
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
// src/bridge/index.ts
|
|
488
|
-
import { randomUUID } from "crypto";
|
|
489
488
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
490
489
|
|
|
491
490
|
// src/bridge/cli-relay.ts
|
|
@@ -731,6 +730,198 @@ function defaultUsage() {
|
|
|
731
730
|
};
|
|
732
731
|
}
|
|
733
732
|
|
|
733
|
+
// src/bridge/create-emit-stream-event.ts
|
|
734
|
+
import { randomUUID } from "crypto";
|
|
735
|
+
var NATIVE_TO_COMMON = {
|
|
736
|
+
shell: "bash",
|
|
737
|
+
web_search: "webSearch"
|
|
738
|
+
};
|
|
739
|
+
function toCommonName(nativeName) {
|
|
740
|
+
return NATIVE_TO_COMMON[nativeName] ?? nativeName;
|
|
741
|
+
}
|
|
742
|
+
function createEmitStreamEvent({
|
|
743
|
+
send,
|
|
744
|
+
stepTracker,
|
|
745
|
+
setTurnUsage,
|
|
746
|
+
setThreadId,
|
|
747
|
+
emitWarning,
|
|
748
|
+
emitError
|
|
749
|
+
}) {
|
|
750
|
+
const textByItem = /* @__PURE__ */ new Map();
|
|
751
|
+
const reasoningByItem = /* @__PURE__ */ new Map();
|
|
752
|
+
return (event) => {
|
|
753
|
+
if (event.type === "thread.started" && typeof event.thread_id === "string") {
|
|
754
|
+
setThreadId(event.thread_id);
|
|
755
|
+
send({ type: "bridge-thread", threadId: event.thread_id });
|
|
756
|
+
}
|
|
757
|
+
if (event.type === "turn.completed") {
|
|
758
|
+
if (event.usage) setTurnUsage(mapUsage(event.usage));
|
|
759
|
+
stepTracker.finishStep();
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (event.type === "turn.failed") {
|
|
763
|
+
emitError({
|
|
764
|
+
error: event.error?.message ?? "codex turn failed",
|
|
765
|
+
message: "codex turn failed"
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (event.type === "error") {
|
|
770
|
+
emitError({
|
|
771
|
+
error: event.message ?? "codex error",
|
|
772
|
+
message: "codex stream error"
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!event.item) return;
|
|
777
|
+
const item = event.item;
|
|
778
|
+
const id = item.id ?? randomUUID();
|
|
779
|
+
const observeStep = () => {
|
|
780
|
+
stepTracker.observeEvent({ event, itemId: id });
|
|
781
|
+
};
|
|
782
|
+
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
783
|
+
if (!textByItem.has(id)) {
|
|
784
|
+
send({ type: "text-start", id });
|
|
785
|
+
textByItem.set(id, "");
|
|
786
|
+
}
|
|
787
|
+
const last = textByItem.get(id) ?? "";
|
|
788
|
+
const next = item.text;
|
|
789
|
+
if (next.length > last.length) {
|
|
790
|
+
send({ type: "text-delta", id, delta: next.slice(last.length) });
|
|
791
|
+
textByItem.set(id, next);
|
|
792
|
+
}
|
|
793
|
+
if (event.type === "item.completed") send({ type: "text-end", id });
|
|
794
|
+
observeStep();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
798
|
+
if (!reasoningByItem.has(id)) {
|
|
799
|
+
send({ type: "reasoning-start", id });
|
|
800
|
+
reasoningByItem.set(id, "");
|
|
801
|
+
}
|
|
802
|
+
const last = reasoningByItem.get(id) ?? "";
|
|
803
|
+
const next = item.text;
|
|
804
|
+
if (next.length > last.length) {
|
|
805
|
+
send({ type: "reasoning-delta", id, delta: next.slice(last.length) });
|
|
806
|
+
reasoningByItem.set(id, next);
|
|
807
|
+
}
|
|
808
|
+
if (event.type === "item.completed") send({ type: "reasoning-end", id });
|
|
809
|
+
observeStep();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (item.type === "command_execution") {
|
|
813
|
+
const nativeName = "shell";
|
|
814
|
+
if (event.type === "item.started") {
|
|
815
|
+
send({
|
|
816
|
+
type: "tool-call",
|
|
817
|
+
toolCallId: id,
|
|
818
|
+
toolName: toCommonName(nativeName),
|
|
819
|
+
nativeName,
|
|
820
|
+
input: JSON.stringify({ command: item.command ?? "" }),
|
|
821
|
+
providerExecuted: true
|
|
822
|
+
});
|
|
823
|
+
} else if (event.type === "item.completed") {
|
|
824
|
+
send({
|
|
825
|
+
type: "tool-result",
|
|
826
|
+
toolCallId: id,
|
|
827
|
+
toolName: toCommonName(nativeName),
|
|
828
|
+
result: {
|
|
829
|
+
exitCode: item.exit_code ?? null,
|
|
830
|
+
output: item.aggregated_output ?? "",
|
|
831
|
+
status: item.status ?? "completed"
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
observeStep();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (item.type === "mcp_tool_call") {
|
|
839
|
+
if (event.type === "item.started") {
|
|
840
|
+
send({
|
|
841
|
+
type: "tool-call",
|
|
842
|
+
toolCallId: id,
|
|
843
|
+
toolName: item.tool ?? "unknown",
|
|
844
|
+
nativeName: item.tool ?? "unknown",
|
|
845
|
+
input: JSON.stringify(item.arguments ?? {}),
|
|
846
|
+
providerExecuted: true
|
|
847
|
+
});
|
|
848
|
+
} else if (event.type === "item.completed") {
|
|
849
|
+
send({
|
|
850
|
+
type: "tool-result",
|
|
851
|
+
toolCallId: id,
|
|
852
|
+
toolName: item.tool ?? "unknown",
|
|
853
|
+
result: extractMcpToolCallResult(item)
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
observeStep();
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (item.type === "web_search") {
|
|
860
|
+
const nativeName = "web_search";
|
|
861
|
+
if (event.type === "item.started") {
|
|
862
|
+
send({
|
|
863
|
+
type: "tool-call",
|
|
864
|
+
toolCallId: id,
|
|
865
|
+
toolName: toCommonName(nativeName),
|
|
866
|
+
nativeName,
|
|
867
|
+
input: JSON.stringify({ query: item.query ?? "" }),
|
|
868
|
+
providerExecuted: true
|
|
869
|
+
});
|
|
870
|
+
} else if (event.type === "item.completed") {
|
|
871
|
+
send({
|
|
872
|
+
type: "tool-result",
|
|
873
|
+
toolCallId: id,
|
|
874
|
+
toolName: toCommonName(nativeName),
|
|
875
|
+
result: item.result ?? null
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
observeStep();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (item.type === "file_change" && event.type === "item.completed") {
|
|
882
|
+
for (const change of item.changes ?? []) {
|
|
883
|
+
send({
|
|
884
|
+
type: "file-change",
|
|
885
|
+
event: change.kind === "add" ? "create" : change.kind === "delete" ? "delete" : "modify",
|
|
886
|
+
path: change.path
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
observeStep();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (item.type === "error" && event.type === "item.completed") {
|
|
893
|
+
const message = typeof item.message === "string" && item.message.trim() ? item.message : "codex reported a non-fatal error item";
|
|
894
|
+
emitWarning({ message });
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
function extractMcpToolCallResult(item) {
|
|
899
|
+
if (item.result === void 0 || item.result === null || typeof item.result !== "object") {
|
|
900
|
+
return item.error?.message ? { error: item.error.message } : null;
|
|
901
|
+
}
|
|
902
|
+
const result = item.result;
|
|
903
|
+
if (result.structured_content !== void 0 && result.structured_content !== null) {
|
|
904
|
+
return result.structured_content;
|
|
905
|
+
}
|
|
906
|
+
return result.content ?? null;
|
|
907
|
+
}
|
|
908
|
+
function mapUsage(usage) {
|
|
909
|
+
const input = usage.input_tokens ?? 0;
|
|
910
|
+
const cacheRead = usage.cached_input_tokens ?? 0;
|
|
911
|
+
return {
|
|
912
|
+
inputTokens: {
|
|
913
|
+
total: input,
|
|
914
|
+
noCache: Math.max(0, input - cacheRead),
|
|
915
|
+
cacheRead,
|
|
916
|
+
cacheWrite: 0
|
|
917
|
+
},
|
|
918
|
+
outputTokens: {
|
|
919
|
+
total: usage.output_tokens ?? 0,
|
|
920
|
+
text: usage.output_tokens ?? 0
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
734
925
|
// src/bridge/tool-relay.ts
|
|
735
926
|
import { createServer } from "http";
|
|
736
927
|
|
|
@@ -948,13 +1139,6 @@ function closeServer(server) {
|
|
|
948
1139
|
// src/bridge/index.ts
|
|
949
1140
|
import { argv, env as procEnv2, stdout as stdout2 } from "process";
|
|
950
1141
|
import * as codexSdkModule from "@openai/codex-sdk";
|
|
951
|
-
var NATIVE_TO_COMMON = {
|
|
952
|
-
shell: "bash",
|
|
953
|
-
web_search: "webSearch"
|
|
954
|
-
};
|
|
955
|
-
function toCommonName(nativeName) {
|
|
956
|
-
return NATIVE_TO_COMMON[nativeName] ?? nativeName;
|
|
957
|
-
}
|
|
958
1142
|
var args = parseArgs(argv.slice(2));
|
|
959
1143
|
var workdir = requireArg({ value: args.workdir, name: "--workdir" });
|
|
960
1144
|
var bridgeStateDir = requireArg({
|
|
@@ -1047,19 +1231,21 @@ async function runTurn(start, turn) {
|
|
|
1047
1231
|
emit({ type: "stream-start" });
|
|
1048
1232
|
const userMessage = start.prompt;
|
|
1049
1233
|
let turnUsage;
|
|
1050
|
-
const textByItem = /* @__PURE__ */ new Map();
|
|
1051
|
-
const reasoningByItem = /* @__PURE__ */ new Map();
|
|
1052
1234
|
const stepTracker = createCodexStepTracker({ send: emit });
|
|
1235
|
+
const emitStreamEvent = createEmitStreamEvent({
|
|
1236
|
+
send: emit,
|
|
1237
|
+
stepTracker,
|
|
1238
|
+
setTurnUsage: (usage) => turnUsage = usage,
|
|
1239
|
+
setThreadId: (threadId) => threadState.id = threadId,
|
|
1240
|
+
emitWarning: turn.emitWarning,
|
|
1241
|
+
emitError: turn.emitError
|
|
1242
|
+
});
|
|
1053
1243
|
try {
|
|
1054
1244
|
const { events } = await thread.runStreamed(userMessage, {
|
|
1055
1245
|
signal: turn.abortSignal
|
|
1056
1246
|
});
|
|
1057
1247
|
for await (const event of events) {
|
|
1058
1248
|
if (turn.abortSignal.aborted) break;
|
|
1059
|
-
if (event.type === "thread.started" && typeof event.thread_id === "string") {
|
|
1060
|
-
threadState.id = event.thread_id;
|
|
1061
|
-
emit({ type: "bridge-thread", threadId: event.thread_id });
|
|
1062
|
-
}
|
|
1063
1249
|
if (cliShimPath && event.item?.type === "command_execution") {
|
|
1064
1250
|
const relayCalls = typeof event.item.command === "string" ? parseToolRelayCommands({
|
|
1065
1251
|
command: event.item.command,
|
|
@@ -1075,15 +1261,7 @@ async function runTurn(start, turn) {
|
|
|
1075
1261
|
continue;
|
|
1076
1262
|
}
|
|
1077
1263
|
}
|
|
1078
|
-
|
|
1079
|
-
send: emit,
|
|
1080
|
-
textByItem,
|
|
1081
|
-
reasoningByItem,
|
|
1082
|
-
stepTracker,
|
|
1083
|
-
setTurnUsage: (u) => turnUsage = u,
|
|
1084
|
-
emitWarning: turn.emitWarning,
|
|
1085
|
-
emitError: turn.emitError
|
|
1086
|
-
});
|
|
1264
|
+
emitStreamEvent(event);
|
|
1087
1265
|
}
|
|
1088
1266
|
} catch (err) {
|
|
1089
1267
|
turn.emitError({ error: err, message: "codex turn failed" });
|
|
@@ -1098,175 +1276,6 @@ async function runTurn(start, turn) {
|
|
|
1098
1276
|
});
|
|
1099
1277
|
void turn.pendingUserMessages;
|
|
1100
1278
|
}
|
|
1101
|
-
function extractMcpToolCallResult(item) {
|
|
1102
|
-
if (item.result === void 0 || item.result === null || typeof item.result !== "object") {
|
|
1103
|
-
return item.error?.message ? { error: item.error.message } : null;
|
|
1104
|
-
}
|
|
1105
|
-
const result = item.result;
|
|
1106
|
-
if (result.structured_content !== void 0 && result.structured_content !== null) {
|
|
1107
|
-
return result.structured_content;
|
|
1108
|
-
}
|
|
1109
|
-
return result.content ?? null;
|
|
1110
|
-
}
|
|
1111
|
-
function translateAndEmit(event, ctx) {
|
|
1112
|
-
if (event.type === "turn.completed") {
|
|
1113
|
-
if (event.usage) ctx.setTurnUsage(mapUsage(event.usage));
|
|
1114
|
-
ctx.stepTracker.finishStep();
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
if (event.type === "turn.failed") {
|
|
1118
|
-
ctx.emitError({
|
|
1119
|
-
error: event.error?.message ?? "codex turn failed",
|
|
1120
|
-
message: "codex turn failed"
|
|
1121
|
-
});
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
if (event.type === "error") {
|
|
1125
|
-
ctx.emitError({
|
|
1126
|
-
error: event.message ?? "codex error",
|
|
1127
|
-
message: "codex stream error"
|
|
1128
|
-
});
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
-
if (!event.item) return;
|
|
1132
|
-
const item = event.item;
|
|
1133
|
-
const id = item.id ?? randomUUID();
|
|
1134
|
-
const observeStep = () => {
|
|
1135
|
-
ctx.stepTracker.observeEvent({ event, itemId: id });
|
|
1136
|
-
};
|
|
1137
|
-
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
1138
|
-
if (!ctx.textByItem.has(id)) {
|
|
1139
|
-
ctx.send({ type: "text-start", id });
|
|
1140
|
-
ctx.textByItem.set(id, "");
|
|
1141
|
-
}
|
|
1142
|
-
const last = ctx.textByItem.get(id) ?? "";
|
|
1143
|
-
const next = item.text;
|
|
1144
|
-
if (next.length > last.length) {
|
|
1145
|
-
ctx.send({ type: "text-delta", id, delta: next.slice(last.length) });
|
|
1146
|
-
ctx.textByItem.set(id, next);
|
|
1147
|
-
}
|
|
1148
|
-
if (event.type === "item.completed") ctx.send({ type: "text-end", id });
|
|
1149
|
-
observeStep();
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
if (item.type === "reasoning" && typeof item.text === "string") {
|
|
1153
|
-
if (!ctx.reasoningByItem.has(id)) {
|
|
1154
|
-
ctx.send({ type: "reasoning-start", id });
|
|
1155
|
-
ctx.reasoningByItem.set(id, "");
|
|
1156
|
-
}
|
|
1157
|
-
const last = ctx.reasoningByItem.get(id) ?? "";
|
|
1158
|
-
const next = item.text;
|
|
1159
|
-
if (next.length > last.length) {
|
|
1160
|
-
ctx.send({ type: "reasoning-delta", id, delta: next.slice(last.length) });
|
|
1161
|
-
ctx.reasoningByItem.set(id, next);
|
|
1162
|
-
}
|
|
1163
|
-
if (event.type === "item.completed")
|
|
1164
|
-
ctx.send({ type: "reasoning-end", id });
|
|
1165
|
-
observeStep();
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
if (item.type === "command_execution") {
|
|
1169
|
-
const nativeName = "shell";
|
|
1170
|
-
if (event.type === "item.started") {
|
|
1171
|
-
ctx.send({
|
|
1172
|
-
type: "tool-call",
|
|
1173
|
-
toolCallId: id,
|
|
1174
|
-
toolName: toCommonName(nativeName),
|
|
1175
|
-
nativeName,
|
|
1176
|
-
input: JSON.stringify({ command: item.command ?? "" }),
|
|
1177
|
-
providerExecuted: true
|
|
1178
|
-
});
|
|
1179
|
-
} else if (event.type === "item.completed") {
|
|
1180
|
-
ctx.send({
|
|
1181
|
-
type: "tool-result",
|
|
1182
|
-
toolCallId: id,
|
|
1183
|
-
toolName: toCommonName(nativeName),
|
|
1184
|
-
result: {
|
|
1185
|
-
exitCode: item.exit_code ?? null,
|
|
1186
|
-
output: item.aggregated_output ?? "",
|
|
1187
|
-
status: item.status ?? "completed"
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
observeStep();
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
if (item.type === "mcp_tool_call") {
|
|
1195
|
-
if (event.type === "item.started") {
|
|
1196
|
-
ctx.send({
|
|
1197
|
-
type: "tool-call",
|
|
1198
|
-
toolCallId: id,
|
|
1199
|
-
toolName: item.tool ?? "unknown",
|
|
1200
|
-
nativeName: item.tool ?? "unknown",
|
|
1201
|
-
input: JSON.stringify(item.arguments ?? {}),
|
|
1202
|
-
providerExecuted: true
|
|
1203
|
-
});
|
|
1204
|
-
} else if (event.type === "item.completed") {
|
|
1205
|
-
ctx.send({
|
|
1206
|
-
type: "tool-result",
|
|
1207
|
-
toolCallId: id,
|
|
1208
|
-
toolName: item.tool ?? "unknown",
|
|
1209
|
-
result: extractMcpToolCallResult(item)
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
observeStep();
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
if (item.type === "web_search") {
|
|
1216
|
-
const nativeName = "web_search";
|
|
1217
|
-
if (event.type === "item.started") {
|
|
1218
|
-
ctx.send({
|
|
1219
|
-
type: "tool-call",
|
|
1220
|
-
toolCallId: id,
|
|
1221
|
-
toolName: toCommonName(nativeName),
|
|
1222
|
-
nativeName,
|
|
1223
|
-
input: JSON.stringify({ query: item.query ?? "" }),
|
|
1224
|
-
providerExecuted: true
|
|
1225
|
-
});
|
|
1226
|
-
} else if (event.type === "item.completed") {
|
|
1227
|
-
ctx.send({
|
|
1228
|
-
type: "tool-result",
|
|
1229
|
-
toolCallId: id,
|
|
1230
|
-
toolName: toCommonName(nativeName),
|
|
1231
|
-
result: item.result ?? null
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
observeStep();
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
if (item.type === "file_change" && event.type === "item.completed") {
|
|
1238
|
-
for (const change of item.changes ?? []) {
|
|
1239
|
-
ctx.send({
|
|
1240
|
-
type: "file-change",
|
|
1241
|
-
event: change.kind === "add" ? "create" : change.kind === "delete" ? "delete" : "modify",
|
|
1242
|
-
path: change.path
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
observeStep();
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1248
|
-
if (item.type === "error" && event.type === "item.completed") {
|
|
1249
|
-
const message = typeof item.message === "string" && item.message.trim() ? item.message : "codex reported a non-fatal error item";
|
|
1250
|
-
ctx.emitWarning({ message });
|
|
1251
|
-
return;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
function mapUsage(usage) {
|
|
1255
|
-
const input = usage.input_tokens ?? 0;
|
|
1256
|
-
const cacheRead = usage.cached_input_tokens ?? 0;
|
|
1257
|
-
return {
|
|
1258
|
-
inputTokens: {
|
|
1259
|
-
total: input,
|
|
1260
|
-
noCache: Math.max(0, input - cacheRead),
|
|
1261
|
-
cacheRead,
|
|
1262
|
-
cacheWrite: 0
|
|
1263
|
-
},
|
|
1264
|
-
outputTokens: {
|
|
1265
|
-
total: usage.output_tokens ?? 0,
|
|
1266
|
-
text: usage.output_tokens ?? 0
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
1279
|
async function startToolRelay({
|
|
1271
1280
|
tools,
|
|
1272
1281
|
emit,
|