@ai-sdk/harness-codex 1.0.126 → 1.0.129
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 +26 -0
- package/README.md +12 -13
- package/dist/bridge/index.mjs +1248 -484
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/bridge/package.json +1 -1
- package/dist/bridge/pnpm-lock.yaml +1 -9
- package/dist/index.d.ts +1399 -0
- package/dist/index.js +46 -73
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bridge/codex-app-server-client.ts +320 -0
- package/src/bridge/codex-app-server-driver.ts +617 -0
- package/src/bridge/codex-step-tracker.ts +2 -0
- package/src/bridge/codex-tool-filtering-hook.ts +148 -0
- package/src/bridge/codex-tool-filtering.ts +42 -0
- package/src/bridge/create-app-server-event-handler.ts +531 -0
- package/src/bridge/create-emit-stream-event.ts +27 -2
- package/src/bridge/index.ts +64 -193
- package/src/bridge/package.json +1 -1
- package/src/bridge/pnpm-lock.yaml +1 -9
- package/src/codex-bridge-protocol.ts +4 -4
- package/src/codex-harness.ts +56 -117
- package/src/bridge/cli-relay.ts +0 -268
- package/src/bridge/tool-relay-auth.ts +0 -156
- package/src/bridge/tool-relay.ts +0 -119
package/dist/bridge/index.mjs
CHANGED
|
@@ -643,202 +643,1214 @@ function serialiseError(err) {
|
|
|
643
643
|
return err;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
-
// src/bridge/
|
|
647
|
-
import {
|
|
646
|
+
// src/bridge/codex-app-server-driver.ts
|
|
647
|
+
import { createHash as createHash2 } from "crypto";
|
|
648
|
+
import { fileURLToPath } from "url";
|
|
648
649
|
|
|
649
|
-
// src/bridge/
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
// src/bridge/codex-tool-filtering.ts
|
|
651
|
+
function resolveCodexBuiltinToolPolicy({
|
|
652
|
+
builtinToolFiltering,
|
|
653
|
+
webSearch
|
|
653
654
|
}) {
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
655
|
+
const included = (toolName) => builtinToolFiltering == null || (builtinToolFiltering.mode === "allow" ? builtinToolFiltering.toolNames.includes(toolName) : !builtinToolFiltering.toolNames.includes(toolName));
|
|
656
|
+
const disabled = {
|
|
657
|
+
bash: !included("bash"),
|
|
658
|
+
webSearch: !included("webSearch"),
|
|
659
|
+
apply_patch: !included("apply_patch"),
|
|
660
|
+
view_image: !included("view_image")
|
|
661
|
+
};
|
|
662
|
+
const disableEnvironments = disabled.bash && disabled.apply_patch && disabled.view_image;
|
|
663
|
+
return {
|
|
664
|
+
disabled,
|
|
665
|
+
disableEnvironments,
|
|
666
|
+
denyApplyPatchWithHook: disabled.apply_patch && !disableEnvironments,
|
|
667
|
+
webSearchMode: webSearch && !disabled.webSearch ? "live" : "disabled"
|
|
668
|
+
};
|
|
659
669
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
670
|
+
|
|
671
|
+
// src/bridge/codex-app-server-client.ts
|
|
672
|
+
import { spawn } from "child_process";
|
|
673
|
+
var CodexAppServerRequestError = class extends Error {
|
|
674
|
+
constructor({ method, error }) {
|
|
675
|
+
super(`Codex app-server ${method} failed: ${error.message}`);
|
|
676
|
+
this.name = "CodexAppServerRequestError";
|
|
677
|
+
this.method = method;
|
|
678
|
+
this.code = error.code;
|
|
679
|
+
this.data = error.data;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
var CodexAppServerClient = class {
|
|
683
|
+
constructor({
|
|
684
|
+
executable,
|
|
685
|
+
args: args2,
|
|
686
|
+
cwd,
|
|
687
|
+
env,
|
|
688
|
+
onNotification,
|
|
689
|
+
onRequest,
|
|
690
|
+
onStderr
|
|
691
|
+
}) {
|
|
692
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
693
|
+
this.nextRequestId = 1;
|
|
694
|
+
this.stdoutBuffer = "";
|
|
695
|
+
this.parseQueue = Promise.resolve();
|
|
696
|
+
this.closed = false;
|
|
697
|
+
this.resolveFailure = () => {
|
|
698
|
+
};
|
|
699
|
+
this.exitResult = null;
|
|
700
|
+
this.onNotification = onNotification;
|
|
701
|
+
this.onRequest = onRequest;
|
|
702
|
+
this.onStderr = onStderr;
|
|
703
|
+
this.failed = new Promise((resolve) => {
|
|
704
|
+
this.resolveFailure = resolve;
|
|
705
|
+
});
|
|
706
|
+
this.child = spawn(executable, args2, {
|
|
707
|
+
cwd,
|
|
708
|
+
env,
|
|
709
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
710
|
+
});
|
|
711
|
+
this.exited = new Promise((resolve) => {
|
|
712
|
+
this.child.once("exit", (code, signal) => {
|
|
713
|
+
this.exitResult = { code, signal };
|
|
714
|
+
this.fail(
|
|
715
|
+
new Error(
|
|
716
|
+
`Codex app-server exited (code ${code ?? "null"}, signal ${signal ?? "null"}).`
|
|
717
|
+
)
|
|
718
|
+
);
|
|
719
|
+
resolve(this.exitResult);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
this.child.once("error", (error) => this.fail(error));
|
|
723
|
+
this.child.stdout.setEncoding("utf8");
|
|
724
|
+
this.child.stdout.on("data", (chunk) => this.consumeStdout(String(chunk)));
|
|
725
|
+
this.child.stderr.setEncoding("utf8");
|
|
726
|
+
this.child.stderr.on("data", (chunk) => this.onStderr(String(chunk)));
|
|
727
|
+
}
|
|
728
|
+
async initialize({
|
|
729
|
+
clientName,
|
|
730
|
+
clientVersion
|
|
731
|
+
}) {
|
|
732
|
+
await this.request({
|
|
733
|
+
method: "initialize",
|
|
734
|
+
params: {
|
|
735
|
+
clientInfo: { name: clientName, version: clientVersion },
|
|
736
|
+
capabilities: { experimentalApi: true }
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
this.notify({ method: "initialized" });
|
|
740
|
+
}
|
|
741
|
+
request({
|
|
742
|
+
method,
|
|
743
|
+
params
|
|
744
|
+
}) {
|
|
745
|
+
if (this.closed || this.exitResult != null) {
|
|
746
|
+
return Promise.reject(new Error("Codex app-server is not running."));
|
|
747
|
+
}
|
|
748
|
+
const id = this.nextRequestId++;
|
|
749
|
+
const response = new Promise((resolve, reject) => {
|
|
750
|
+
this.pending.set(id, { method, resolve, reject });
|
|
751
|
+
});
|
|
752
|
+
try {
|
|
753
|
+
this.write({ id, method, ...params === void 0 ? {} : { params } });
|
|
754
|
+
} catch (error) {
|
|
755
|
+
this.pending.delete(id);
|
|
756
|
+
return Promise.reject(error);
|
|
757
|
+
}
|
|
758
|
+
return response;
|
|
759
|
+
}
|
|
760
|
+
notify({ method, params }) {
|
|
761
|
+
this.write({ method, ...params === void 0 ? {} : { params } });
|
|
762
|
+
}
|
|
763
|
+
async close() {
|
|
764
|
+
if (this.closed) return;
|
|
765
|
+
this.closed = true;
|
|
766
|
+
this.child.stdin.end();
|
|
767
|
+
if (await this.waitForExit({ timeoutMs: 500 })) return;
|
|
768
|
+
this.child.kill("SIGTERM");
|
|
769
|
+
if (await this.waitForExit({ timeoutMs: 1e3 })) return;
|
|
770
|
+
this.child.kill("SIGKILL");
|
|
771
|
+
await this.waitForExit({ timeoutMs: 1e3 });
|
|
772
|
+
}
|
|
773
|
+
waitUntilExit() {
|
|
774
|
+
return this.exited;
|
|
775
|
+
}
|
|
776
|
+
waitUntilFailure() {
|
|
777
|
+
return this.failed.then((error) => {
|
|
778
|
+
throw error;
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
write(message) {
|
|
782
|
+
if (this.closed || this.exitResult != null || !this.child.stdin.writable) {
|
|
783
|
+
throw new Error("Codex app-server is not writable.");
|
|
784
|
+
}
|
|
785
|
+
this.child.stdin.write(`${JSON.stringify(message)}
|
|
786
|
+
`);
|
|
787
|
+
}
|
|
788
|
+
consumeStdout(chunk) {
|
|
789
|
+
this.stdoutBuffer += chunk;
|
|
790
|
+
let newline = this.stdoutBuffer.indexOf("\n");
|
|
791
|
+
while (newline !== -1) {
|
|
792
|
+
const line = this.stdoutBuffer.slice(0, newline).trim();
|
|
793
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
|
|
794
|
+
if (line.length > 0) {
|
|
795
|
+
this.parseQueue = this.parseQueue.then(() => this.processLine(line)).catch((error) => this.fail(error));
|
|
796
|
+
}
|
|
797
|
+
newline = this.stdoutBuffer.indexOf("\n");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async processLine(line) {
|
|
801
|
+
let message;
|
|
802
|
+
try {
|
|
803
|
+
message = JSON.parse(line);
|
|
804
|
+
} catch {
|
|
805
|
+
throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
|
|
806
|
+
}
|
|
807
|
+
if (!isRecord(message)) {
|
|
808
|
+
throw new Error("Codex app-server wrote an invalid JSON-RPC message.");
|
|
809
|
+
}
|
|
810
|
+
if (typeof message.method === "string") {
|
|
811
|
+
if (isJsonRpcId(message.id)) {
|
|
812
|
+
void this.handleServerRequest({
|
|
813
|
+
id: message.id,
|
|
814
|
+
method: message.method,
|
|
815
|
+
...message.params === void 0 ? {} : { params: message.params }
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
this.onNotification({
|
|
819
|
+
method: message.method,
|
|
820
|
+
...message.params === void 0 ? {} : { params: message.params }
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (!isJsonRpcId(message.id)) {
|
|
826
|
+
throw new Error("Codex app-server response is missing a valid id.");
|
|
827
|
+
}
|
|
828
|
+
const pending = this.pending.get(message.id);
|
|
829
|
+
if (pending == null) return;
|
|
830
|
+
this.pending.delete(message.id);
|
|
831
|
+
if (isJsonRpcError(message.error)) {
|
|
832
|
+
pending.reject(
|
|
833
|
+
new CodexAppServerRequestError({
|
|
834
|
+
method: pending.method,
|
|
835
|
+
error: message.error
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (!Object.prototype.hasOwnProperty.call(message, "result")) {
|
|
841
|
+
pending.reject(
|
|
842
|
+
new Error(`Codex app-server ${pending.method} response has no result.`)
|
|
843
|
+
);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
pending.resolve(message.result);
|
|
847
|
+
}
|
|
848
|
+
async handleServerRequest(request) {
|
|
849
|
+
try {
|
|
850
|
+
const result = await this.onRequest(request);
|
|
851
|
+
if (!this.closed && this.exitResult == null) {
|
|
852
|
+
this.write({ id: request.id, result });
|
|
853
|
+
}
|
|
854
|
+
} catch (error) {
|
|
855
|
+
if (!this.closed && this.exitResult == null) {
|
|
856
|
+
this.write({
|
|
857
|
+
id: request.id,
|
|
858
|
+
error: {
|
|
859
|
+
code: -32603,
|
|
860
|
+
message: error instanceof Error ? error.message : String(error)
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
failPending(error) {
|
|
867
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
868
|
+
this.pending.clear();
|
|
869
|
+
}
|
|
870
|
+
fail(error) {
|
|
871
|
+
this.failPending(error);
|
|
872
|
+
if (this.failure !== void 0) return;
|
|
873
|
+
this.failure = error;
|
|
874
|
+
this.resolveFailure(error);
|
|
875
|
+
}
|
|
876
|
+
async waitForExit({
|
|
877
|
+
timeoutMs
|
|
878
|
+
}) {
|
|
879
|
+
if (this.exitResult != null) return true;
|
|
880
|
+
return Promise.race([
|
|
881
|
+
this.exited.then(() => true),
|
|
882
|
+
new Promise((resolve) => {
|
|
883
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
884
|
+
timer.unref?.();
|
|
885
|
+
})
|
|
886
|
+
]);
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
function isRecord(value) {
|
|
890
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
666
891
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
method: 'POST',
|
|
670
|
-
headers: { 'Content-Type': 'application/json' },
|
|
671
|
-
body: JSON.stringify({ requestId, toolName, input }),
|
|
672
|
-
});
|
|
673
|
-
const text = await response.text();
|
|
674
|
-
let payload;
|
|
675
|
-
try {
|
|
676
|
-
payload = text ? JSON.parse(text) : {};
|
|
677
|
-
} catch {
|
|
678
|
-
payload = { error: text };
|
|
892
|
+
function isJsonRpcId(value) {
|
|
893
|
+
return typeof value === "string" || typeof value === "number";
|
|
679
894
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
process.exit(1);
|
|
895
|
+
function isJsonRpcError(value) {
|
|
896
|
+
return isRecord(value) && typeof value.code === "number" && typeof value.message === "string";
|
|
683
897
|
}
|
|
684
|
-
|
|
685
|
-
|
|
898
|
+
|
|
899
|
+
// src/bridge/create-app-server-event-handler.ts
|
|
900
|
+
function createAppServerEventHandler({
|
|
901
|
+
stepTracker,
|
|
902
|
+
emitStreamEvent,
|
|
903
|
+
emitWarning,
|
|
904
|
+
emitError
|
|
905
|
+
}) {
|
|
906
|
+
let activeThreadId;
|
|
907
|
+
let activeTurnId;
|
|
908
|
+
let settled = false;
|
|
909
|
+
let accumulatedUsage = emptyUsageBreakdown();
|
|
910
|
+
let lastCumulativeUsageKey;
|
|
911
|
+
const textByItem = /* @__PURE__ */ new Map();
|
|
912
|
+
const reasoningByItem = /* @__PURE__ */ new Map();
|
|
913
|
+
const nativeToolCalls = /* @__PURE__ */ new Map();
|
|
914
|
+
let resolveCompletion = () => {
|
|
915
|
+
};
|
|
916
|
+
const completion = new Promise((resolve) => {
|
|
917
|
+
resolveCompletion = resolve;
|
|
918
|
+
});
|
|
919
|
+
const announceThread = (threadId) => {
|
|
920
|
+
if (activeThreadId != null) return;
|
|
921
|
+
activeThreadId = threadId;
|
|
922
|
+
emitStreamEvent({ type: "thread.started", thread_id: threadId });
|
|
923
|
+
};
|
|
924
|
+
const handleItem = ({
|
|
925
|
+
eventType,
|
|
926
|
+
params
|
|
927
|
+
}) => {
|
|
928
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
|
|
929
|
+
const item = asRecord(params.item);
|
|
930
|
+
if (item == null || typeof item.type !== "string") return;
|
|
931
|
+
if (eventType === "item.completed" && typeof item.id === "string") {
|
|
932
|
+
const call = nativeToolCalls.get(item.id);
|
|
933
|
+
if (call?.name === "apply_patch" && item.type === "fileChange") {
|
|
934
|
+
call.successConfirmed = item.status === "completed";
|
|
935
|
+
} else if (call?.name === "view_image" && item.type === "imageView") {
|
|
936
|
+
call.successConfirmed = true;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const normalized = normalizeItem({ item, textByItem, reasoningByItem });
|
|
940
|
+
if (normalized == null) return;
|
|
941
|
+
if (normalized.type === "dynamic_tool_call") {
|
|
942
|
+
stepTracker.observeEvent({
|
|
943
|
+
event: { type: eventType, item: normalized },
|
|
944
|
+
itemId: normalized.id
|
|
945
|
+
});
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
emitStreamEvent({ type: eventType, item: normalized });
|
|
949
|
+
};
|
|
950
|
+
const emitNativeToolResult = ({
|
|
951
|
+
callId,
|
|
952
|
+
result
|
|
953
|
+
}) => {
|
|
954
|
+
const call = nativeToolCalls.get(callId);
|
|
955
|
+
if (call == null || call.resultEmitted) return;
|
|
956
|
+
call.resultEmitted = true;
|
|
957
|
+
emitStreamEvent({
|
|
958
|
+
type: "item.completed",
|
|
959
|
+
item: {
|
|
960
|
+
type: "native_tool",
|
|
961
|
+
id: callId,
|
|
962
|
+
tool: call.name,
|
|
963
|
+
result,
|
|
964
|
+
...!call.successConfirmed ? { isError: true } : {}
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
};
|
|
968
|
+
const handleRawItem = (params) => {
|
|
969
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId })) return;
|
|
970
|
+
const item = asRecord(params.item);
|
|
971
|
+
if (item == null) return;
|
|
972
|
+
const toolCall = normalizeNativeToolCall({ item });
|
|
973
|
+
if (toolCall != null) {
|
|
974
|
+
if (nativeToolCalls.has(toolCall.callId)) return;
|
|
975
|
+
nativeToolCalls.set(toolCall.callId, {
|
|
976
|
+
name: toolCall.name,
|
|
977
|
+
resultEmitted: false,
|
|
978
|
+
successConfirmed: false
|
|
979
|
+
});
|
|
980
|
+
emitStreamEvent({
|
|
981
|
+
type: "item.started",
|
|
982
|
+
item: {
|
|
983
|
+
type: "native_tool",
|
|
984
|
+
id: toolCall.callId,
|
|
985
|
+
tool: toolCall.name,
|
|
986
|
+
input: toolCall.input
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (typeof item.call_id !== "string") return;
|
|
992
|
+
const call = nativeToolCalls.get(item.call_id);
|
|
993
|
+
if (call == null || (call.name === "apply_patch" ? item.type !== "custom_tool_call_output" : item.type !== "function_call_output") || !Object.prototype.hasOwnProperty.call(item, "output")) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
emitNativeToolResult({
|
|
997
|
+
callId: item.call_id,
|
|
998
|
+
result: call.name === "view_image" && call.successConfirmed && Array.isArray(item.output) && item.output.length === 0 ? "Image viewed." : item.output
|
|
999
|
+
});
|
|
1000
|
+
};
|
|
1001
|
+
return {
|
|
1002
|
+
announceThread,
|
|
1003
|
+
setTurnId(turnId) {
|
|
1004
|
+
activeTurnId = turnId;
|
|
1005
|
+
},
|
|
1006
|
+
handle(notification) {
|
|
1007
|
+
const params = asRecord(notification.params);
|
|
1008
|
+
if (notification.method === "thread/started") {
|
|
1009
|
+
const thread = asRecord(params?.thread);
|
|
1010
|
+
if (typeof thread?.id === "string") announceThread(thread.id);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (notification.method === "turn/started") {
|
|
1014
|
+
const turn = asRecord(params?.turn);
|
|
1015
|
+
if (params?.threadId === activeThreadId && typeof turn?.id === "string") {
|
|
1016
|
+
activeTurnId = turn.id;
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (notification.method === "item/started" && params != null) {
|
|
1021
|
+
handleItem({ eventType: "item.started", params });
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (notification.method === "item/completed" && params != null) {
|
|
1025
|
+
handleItem({ eventType: "item.completed", params });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (notification.method === "rawResponseItem/completed" && params != null) {
|
|
1029
|
+
handleRawItem(params);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (notification.method === "item/agentMessage/delta" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId }) && typeof params.itemId === "string" && typeof params.delta === "string") {
|
|
1033
|
+
const text = (textByItem.get(params.itemId) ?? "") + params.delta;
|
|
1034
|
+
textByItem.set(params.itemId, text);
|
|
1035
|
+
emitStreamEvent({
|
|
1036
|
+
type: "item.updated",
|
|
1037
|
+
item: { type: "agent_message", id: params.itemId, text }
|
|
1038
|
+
});
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (notification.method === "item/reasoning/summaryTextDelta" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId }) && typeof params.itemId === "string" && typeof params.delta === "string") {
|
|
1042
|
+
const text = (reasoningByItem.get(params.itemId) ?? "") + params.delta;
|
|
1043
|
+
reasoningByItem.set(params.itemId, text);
|
|
1044
|
+
emitStreamEvent({
|
|
1045
|
+
type: "item.updated",
|
|
1046
|
+
item: { type: "reasoning", id: params.itemId, text }
|
|
1047
|
+
});
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (notification.method === "thread/tokenUsage/updated" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId })) {
|
|
1051
|
+
const tokenUsage = asRecord(params.tokenUsage);
|
|
1052
|
+
const total = readUsageBreakdown(tokenUsage?.total);
|
|
1053
|
+
const last = readUsageBreakdown(tokenUsage?.last);
|
|
1054
|
+
if (total != null && last != null) {
|
|
1055
|
+
const key = usageKey(total);
|
|
1056
|
+
if (key !== lastCumulativeUsageKey) {
|
|
1057
|
+
lastCumulativeUsageKey = key;
|
|
1058
|
+
accumulatedUsage = addUsage({
|
|
1059
|
+
total: accumulatedUsage,
|
|
1060
|
+
increment: last
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (notification.method === "error" && params != null) {
|
|
1067
|
+
if (!matchesActiveTurn({ params, activeThreadId, activeTurnId }))
|
|
1068
|
+
return;
|
|
1069
|
+
const error = asRecord(params.error);
|
|
1070
|
+
const message = typeof error?.message === "string" ? error.message : "Codex app-server reported an error.";
|
|
1071
|
+
if (params.willRetry === true) {
|
|
1072
|
+
emitWarning({ message });
|
|
1073
|
+
} else {
|
|
1074
|
+
emitError({ error: message, message: "codex turn failed" });
|
|
1075
|
+
}
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if ((notification.method === "warning" || notification.method === "configWarning" || notification.method === "deprecationNotice") && typeof params?.message === "string") {
|
|
1079
|
+
emitWarning({ message: params.message });
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (notification.method === "turn/completed" && params != null && matchesActiveTurn({ params, activeThreadId, activeTurnId })) {
|
|
1083
|
+
if (settled) return;
|
|
1084
|
+
settled = true;
|
|
1085
|
+
const turn = asRecord(params.turn);
|
|
1086
|
+
const status = typeof turn?.status === "string" ? turn.status : "failed";
|
|
1087
|
+
const turnError = asRecord(turn?.error);
|
|
1088
|
+
if (status === "completed") {
|
|
1089
|
+
for (const [callId, call] of nativeToolCalls) {
|
|
1090
|
+
if (call.resultEmitted) continue;
|
|
1091
|
+
emitNativeToolResult({
|
|
1092
|
+
callId,
|
|
1093
|
+
result: call.successConfirmed ? call.name === "apply_patch" ? "Patch applied." : "Image viewed." : "Codex did not report the tool result."
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
emitStreamEvent({
|
|
1098
|
+
type: "turn.completed",
|
|
1099
|
+
usage: toLegacyUsage(accumulatedUsage)
|
|
1100
|
+
});
|
|
1101
|
+
resolveCompletion({
|
|
1102
|
+
status,
|
|
1103
|
+
...typeof turnError?.message === "string" ? { error: turnError.message } : {}
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
waitForCompletion: () => completion
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function normalizeNativeToolCall({
|
|
1111
|
+
item
|
|
1112
|
+
}) {
|
|
1113
|
+
if (item.namespace != null || typeof item.call_id !== "string" || item.call_id.length === 0) {
|
|
1114
|
+
return void 0;
|
|
1115
|
+
}
|
|
1116
|
+
if (item.type === "custom_tool_call" && item.name === "apply_patch" && typeof item.input === "string") {
|
|
1117
|
+
return {
|
|
1118
|
+
callId: item.call_id,
|
|
1119
|
+
name: "apply_patch",
|
|
1120
|
+
input: JSON.stringify(item.input)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
if (item.type === "function_call" && item.name === "view_image" && typeof item.arguments === "string") {
|
|
1124
|
+
return {
|
|
1125
|
+
callId: item.call_id,
|
|
1126
|
+
name: "view_image",
|
|
1127
|
+
input: item.arguments
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
return void 0;
|
|
686
1131
|
}
|
|
687
|
-
function
|
|
688
|
-
|
|
689
|
-
|
|
1132
|
+
function normalizeItem({
|
|
1133
|
+
item,
|
|
1134
|
+
textByItem,
|
|
1135
|
+
reasoningByItem
|
|
690
1136
|
}) {
|
|
691
|
-
|
|
1137
|
+
const id = typeof item.id === "string" ? item.id : void 0;
|
|
1138
|
+
if (item.type === "agentMessage") {
|
|
1139
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
1140
|
+
if (id != null) textByItem.set(id, text);
|
|
1141
|
+
return { type: "agent_message", id, text };
|
|
1142
|
+
}
|
|
1143
|
+
if (item.type === "reasoning") {
|
|
1144
|
+
const summary = stringArray(item.summary).join("\n\n");
|
|
1145
|
+
const content = stringArray(item.content).join("\n\n");
|
|
1146
|
+
const text = summary || reasoningByItem.get(id ?? "") || content;
|
|
1147
|
+
if (id != null) reasoningByItem.set(id, text);
|
|
1148
|
+
return { type: "reasoning", id, text };
|
|
1149
|
+
}
|
|
1150
|
+
if (item.type === "commandExecution") {
|
|
1151
|
+
return {
|
|
1152
|
+
type: "command_execution",
|
|
1153
|
+
id,
|
|
1154
|
+
command: typeof item.command === "string" ? item.command : "",
|
|
1155
|
+
exit_code: typeof item.exitCode === "number" ? item.exitCode : void 0,
|
|
1156
|
+
aggregated_output: typeof item.aggregatedOutput === "string" ? item.aggregatedOutput : void 0,
|
|
1157
|
+
status: normalizeCommandStatus(item.status)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (item.type === "mcpToolCall") {
|
|
1161
|
+
const result = asRecord(item.result);
|
|
1162
|
+
return {
|
|
1163
|
+
type: "mcp_tool_call",
|
|
1164
|
+
id,
|
|
1165
|
+
server: typeof item.server === "string" ? item.server : void 0,
|
|
1166
|
+
tool: typeof item.tool === "string" ? item.tool : void 0,
|
|
1167
|
+
arguments: item.arguments,
|
|
1168
|
+
result: result == null ? item.result : {
|
|
1169
|
+
content: result.content,
|
|
1170
|
+
structured_content: result.structuredContent
|
|
1171
|
+
},
|
|
1172
|
+
error: asRecord(item.error)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
if (item.type === "dynamicToolCall") {
|
|
1176
|
+
return { type: "dynamic_tool_call", id };
|
|
1177
|
+
}
|
|
1178
|
+
if (item.type === "webSearch") {
|
|
1179
|
+
return {
|
|
1180
|
+
type: "web_search",
|
|
1181
|
+
id,
|
|
1182
|
+
query: typeof item.query === "string" ? item.query : void 0,
|
|
1183
|
+
action: asRecord(item.action) ?? void 0,
|
|
1184
|
+
result: item.results
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
if (item.type === "fileChange") {
|
|
1188
|
+
return {
|
|
1189
|
+
type: "file_change",
|
|
1190
|
+
id,
|
|
1191
|
+
changes: Array.isArray(item.changes) ? item.changes.flatMap((change) => {
|
|
1192
|
+
const value = asRecord(change);
|
|
1193
|
+
const kind = asRecord(value?.kind)?.type;
|
|
1194
|
+
return typeof value?.path === "string" && (kind === "add" || kind === "delete" || kind === "update") ? [{ path: value.path, kind }] : [];
|
|
1195
|
+
}) : []
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
if (item.type === "plan") return { type: "todo_list", id };
|
|
1199
|
+
return void 0;
|
|
1200
|
+
}
|
|
1201
|
+
function normalizeCommandStatus(value) {
|
|
1202
|
+
if (value === "completed") return "completed";
|
|
1203
|
+
if (value === "inProgress") return "in_progress";
|
|
1204
|
+
return "failed";
|
|
692
1205
|
}
|
|
693
|
-
function
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1206
|
+
function matchesActiveTurn({
|
|
1207
|
+
params,
|
|
1208
|
+
activeThreadId,
|
|
1209
|
+
activeTurnId
|
|
697
1210
|
}) {
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1211
|
+
const nestedTurn = asRecord(params.turn);
|
|
1212
|
+
const turnId = typeof params.turnId === "string" ? params.turnId : typeof nestedTurn?.id === "string" ? nestedTurn.id : void 0;
|
|
1213
|
+
return activeThreadId != null && activeTurnId != null && params.threadId === activeThreadId && turnId === activeTurnId;
|
|
1214
|
+
}
|
|
1215
|
+
function asRecord(value) {
|
|
1216
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1217
|
+
}
|
|
1218
|
+
function stringArray(value) {
|
|
1219
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1220
|
+
}
|
|
1221
|
+
function emptyUsageBreakdown() {
|
|
1222
|
+
return {
|
|
1223
|
+
inputTokens: 0,
|
|
1224
|
+
cachedInputTokens: 0,
|
|
1225
|
+
cacheWriteInputTokens: 0,
|
|
1226
|
+
outputTokens: 0
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
function readUsageBreakdown(value) {
|
|
1230
|
+
const usage = asRecord(value);
|
|
1231
|
+
if (usage == null) return void 0;
|
|
1232
|
+
return {
|
|
1233
|
+
inputTokens: numberOrZero(usage.inputTokens),
|
|
1234
|
+
cachedInputTokens: numberOrZero(usage.cachedInputTokens),
|
|
1235
|
+
cacheWriteInputTokens: numberOrZero(usage.cacheWriteInputTokens),
|
|
1236
|
+
outputTokens: numberOrZero(usage.outputTokens)
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function addUsage({
|
|
1240
|
+
total,
|
|
1241
|
+
increment
|
|
1242
|
+
}) {
|
|
1243
|
+
return {
|
|
1244
|
+
inputTokens: total.inputTokens + increment.inputTokens,
|
|
1245
|
+
cachedInputTokens: total.cachedInputTokens + increment.cachedInputTokens,
|
|
1246
|
+
cacheWriteInputTokens: total.cacheWriteInputTokens + increment.cacheWriteInputTokens,
|
|
1247
|
+
outputTokens: total.outputTokens + increment.outputTokens
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function usageKey(usage) {
|
|
1251
|
+
return [
|
|
1252
|
+
usage.inputTokens,
|
|
1253
|
+
usage.cachedInputTokens,
|
|
1254
|
+
usage.cacheWriteInputTokens,
|
|
1255
|
+
usage.outputTokens
|
|
1256
|
+
].join(":");
|
|
1257
|
+
}
|
|
1258
|
+
function toLegacyUsage(usage) {
|
|
1259
|
+
return {
|
|
1260
|
+
input_tokens: usage.inputTokens,
|
|
1261
|
+
cached_input_tokens: usage.cachedInputTokens,
|
|
1262
|
+
cache_write_input_tokens: usage.cacheWriteInputTokens,
|
|
1263
|
+
output_tokens: usage.outputTokens
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function numberOrZero(value) {
|
|
1267
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/bridge/codex-app-server-driver.ts
|
|
1271
|
+
import { isDeepStrictEqual } from "util";
|
|
1272
|
+
|
|
1273
|
+
// src/bridge/codex-tool-filtering-hook.ts
|
|
1274
|
+
import { createHash } from "crypto";
|
|
1275
|
+
import path from "path";
|
|
1276
|
+
var MATCHER = "^apply_patch$";
|
|
1277
|
+
var TIMEOUT_SECONDS = 10;
|
|
1278
|
+
var COMMAND = `printf '%s\\n' "Tool 'apply_patch' is inactive due to the HarnessAgent tool filtering policy." >&2; exit 2`;
|
|
1279
|
+
function createTrustedApplyPatchHook({
|
|
1280
|
+
codexConfig
|
|
1281
|
+
}) {
|
|
1282
|
+
const threadConfig = { ...codexConfig };
|
|
1283
|
+
const hooks = { ...asRecord2(threadConfig.hooks) };
|
|
1284
|
+
delete threadConfig.hooks;
|
|
1285
|
+
for (const [key2, value] of Object.entries(threadConfig)) {
|
|
1286
|
+
if (!key2.startsWith("hooks.")) continue;
|
|
1287
|
+
const name = key2.slice("hooks.".length);
|
|
1288
|
+
if (name.startsWith("state.")) {
|
|
1289
|
+
hooks.state = {
|
|
1290
|
+
...asRecord2(hooks.state),
|
|
1291
|
+
[name.slice("state.".length)]: value
|
|
1292
|
+
};
|
|
1293
|
+
} else {
|
|
1294
|
+
hooks[name] = value;
|
|
710
1295
|
}
|
|
711
|
-
|
|
1296
|
+
delete threadConfig[key2];
|
|
712
1297
|
}
|
|
713
|
-
const
|
|
714
|
-
if (!
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1298
|
+
const userGroups = hooks.PreToolUse ?? [];
|
|
1299
|
+
if (!Array.isArray(userGroups)) {
|
|
1300
|
+
throw new Error("Codex config hooks.PreToolUse must be an array.");
|
|
1301
|
+
}
|
|
1302
|
+
const handler = {
|
|
1303
|
+
type: "command",
|
|
1304
|
+
command: COMMAND,
|
|
1305
|
+
timeout: TIMEOUT_SECONDS,
|
|
1306
|
+
async: false
|
|
1307
|
+
};
|
|
1308
|
+
const hash = `sha256:${createHash("sha256").update(
|
|
1309
|
+
JSON.stringify(
|
|
1310
|
+
canonicalize({
|
|
1311
|
+
event_name: "pre_tool_use",
|
|
1312
|
+
matcher: MATCHER,
|
|
1313
|
+
hooks: [handler]
|
|
1314
|
+
})
|
|
1315
|
+
)
|
|
1316
|
+
).digest("hex")}`;
|
|
1317
|
+
const sourcePath = process.platform === "win32" ? path.win32.resolve("C:\\", "<session-flags>/config.toml") : path.posix.resolve("/", "<session-flags>/config.toml");
|
|
1318
|
+
const key = `${sourcePath}:pre_tool_use:${userGroups.length}:0`;
|
|
1319
|
+
hooks.PreToolUse = [...userGroups, { matcher: MATCHER, hooks: [handler] }];
|
|
1320
|
+
hooks.state = {
|
|
1321
|
+
...asRecord2(hooks.state),
|
|
1322
|
+
[key]: { enabled: true, trusted_hash: hash }
|
|
1323
|
+
};
|
|
1324
|
+
return {
|
|
1325
|
+
cliOverrides: [`hooks=${toTomlInline(hooks)}`, "features.hooks=true"],
|
|
1326
|
+
threadConfig,
|
|
1327
|
+
key,
|
|
1328
|
+
hash,
|
|
1329
|
+
command: COMMAND
|
|
1330
|
+
};
|
|
724
1331
|
}
|
|
725
|
-
function
|
|
726
|
-
|
|
727
|
-
|
|
1332
|
+
function assertTrustedApplyPatchHook({
|
|
1333
|
+
response,
|
|
1334
|
+
hook
|
|
728
1335
|
}) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1336
|
+
const data = asRecord2(response)?.data;
|
|
1337
|
+
const entry = Array.isArray(data) && data.length === 1 ? data[0] : null;
|
|
1338
|
+
const record = asRecord2(entry);
|
|
1339
|
+
const matches = Array.isArray(record?.hooks) ? record.hooks.filter((item) => asRecord2(item)?.key === hook.key) : [];
|
|
1340
|
+
const match = matches.length === 1 ? asRecord2(matches[0]) : void 0;
|
|
1341
|
+
if (!match || match.eventName !== "preToolUse" || match.handlerType !== "command" || match.command !== hook.command || match.matcher !== MATCHER || match.currentHash !== hook.hash || match.enabled !== true || match.trustStatus !== "trusted" || Array.isArray(record?.errors) && record.errors.length > 0) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
"Codex app-server did not load the trusted apply_patch filtering hook."
|
|
1344
|
+
);
|
|
737
1345
|
}
|
|
738
1346
|
}
|
|
739
|
-
function
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1347
|
+
function canonicalize(value) {
|
|
1348
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1349
|
+
if (value != null && typeof value === "object") {
|
|
1350
|
+
return Object.fromEntries(
|
|
1351
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonicalize(item)])
|
|
1352
|
+
);
|
|
744
1353
|
}
|
|
745
|
-
|
|
746
|
-
return argv2[2];
|
|
1354
|
+
return value;
|
|
747
1355
|
}
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (char === '"') {
|
|
760
|
-
quote = void 0;
|
|
761
|
-
} else if (char === "\\") {
|
|
762
|
-
i++;
|
|
763
|
-
}
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (char === '"' || char === "'") {
|
|
767
|
-
quote = char;
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
if (char === "\\") {
|
|
771
|
-
i++;
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
if (char === "&" && command[i + 1] === "&") {
|
|
775
|
-
const nestedCommand2 = command.slice(start, i).trim();
|
|
776
|
-
if (!nestedCommand2) return void 0;
|
|
777
|
-
commands.push(nestedCommand2);
|
|
778
|
-
start = i + 2;
|
|
779
|
-
i++;
|
|
780
|
-
}
|
|
1356
|
+
function toTomlInline(value) {
|
|
1357
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
1358
|
+
if (typeof value === "boolean") return String(value);
|
|
1359
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1360
|
+
return String(value);
|
|
1361
|
+
}
|
|
1362
|
+
if (Array.isArray(value)) {
|
|
1363
|
+
return `[${value.map(toTomlInline).join(",")}]`;
|
|
1364
|
+
}
|
|
1365
|
+
if (value != null && typeof value === "object") {
|
|
1366
|
+
return `{${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${JSON.stringify(key)}=${toTomlInline(item)}`).join(",")}}`;
|
|
781
1367
|
}
|
|
782
|
-
|
|
783
|
-
const nestedCommand = command.slice(start).trim();
|
|
784
|
-
if (!nestedCommand) return void 0;
|
|
785
|
-
commands.push(nestedCommand);
|
|
786
|
-
return commands;
|
|
1368
|
+
throw new Error("Codex hook configuration contains an unsupported value.");
|
|
787
1369
|
}
|
|
788
|
-
function
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1370
|
+
function asRecord2(value) {
|
|
1371
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/bridge/codex-app-server-driver.ts
|
|
1375
|
+
var CODEX_CLI_PATH = fileURLToPath(
|
|
1376
|
+
new URL("./node_modules/@openai/codex/bin/codex.js", import.meta.url)
|
|
1377
|
+
);
|
|
1378
|
+
function createCodexAppServerRuntime() {
|
|
1379
|
+
let client;
|
|
1380
|
+
let loadedThreadId;
|
|
1381
|
+
let loadedConfig;
|
|
1382
|
+
let activeTurn;
|
|
1383
|
+
const close = async () => {
|
|
1384
|
+
const previous = client;
|
|
1385
|
+
client = void 0;
|
|
1386
|
+
loadedThreadId = void 0;
|
|
1387
|
+
loadedConfig = void 0;
|
|
1388
|
+
await previous?.close();
|
|
798
1389
|
};
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
if (
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1390
|
+
const runTurn2 = async (options) => {
|
|
1391
|
+
const { start, turn, emit, workdir: workdir2, threadId, codexModel, codexConfig } = options;
|
|
1392
|
+
if (activeTurn != null) throw new Error("A Codex turn is already active.");
|
|
1393
|
+
const toolPolicy = resolveCodexBuiltinToolPolicy({
|
|
1394
|
+
builtinToolFiltering: start.builtinToolFiltering,
|
|
1395
|
+
webSearch: start.webSearch
|
|
1396
|
+
});
|
|
1397
|
+
const trustedHook = toolPolicy.denyApplyPatchWithHook ? createTrustedApplyPatchHook({ codexConfig }) : void 0;
|
|
1398
|
+
const nextConfig = {
|
|
1399
|
+
...trustedHook?.threadConfig ?? codexConfig,
|
|
1400
|
+
web_search: toolPolicy.webSearchMode,
|
|
1401
|
+
...toolPolicy.disabled.bash || toolPolicy.disabled.view_image ? {
|
|
1402
|
+
features: {
|
|
1403
|
+
...asRecord3(codexConfig.features),
|
|
1404
|
+
...toolPolicy.disabled.bash ? { shell_tool: false } : {},
|
|
1405
|
+
...toolPolicy.disabled.view_image ? { view_image: false } : {}
|
|
1406
|
+
}
|
|
1407
|
+
} : {}
|
|
1408
|
+
};
|
|
1409
|
+
const processConfig = {
|
|
1410
|
+
thread: nextConfig,
|
|
1411
|
+
cliOverrides: trustedHook?.cliOverrides ?? []
|
|
1412
|
+
};
|
|
1413
|
+
if (client != null && !isDeepStrictEqual(loadedConfig, processConfig)) {
|
|
1414
|
+
await close();
|
|
809
1415
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1416
|
+
const handler = createAppServerEventHandler({
|
|
1417
|
+
stepTracker: options.stepTracker,
|
|
1418
|
+
emitStreamEvent: options.emitStreamEvent,
|
|
1419
|
+
emitWarning: turn.emitWarning,
|
|
1420
|
+
emitError: turn.emitError
|
|
1421
|
+
});
|
|
1422
|
+
const dynamicTools = createDynamicTools(start.tools ?? []);
|
|
1423
|
+
let rejectProtocolFailure = () => {
|
|
1424
|
+
};
|
|
1425
|
+
const protocolFailure = new Promise((_, reject) => {
|
|
1426
|
+
rejectProtocolFailure = reject;
|
|
1427
|
+
});
|
|
1428
|
+
const currentTurn = {
|
|
1429
|
+
threadId,
|
|
1430
|
+
turnId: void 0,
|
|
1431
|
+
handler,
|
|
1432
|
+
dynamicTools,
|
|
1433
|
+
options,
|
|
1434
|
+
fail: rejectProtocolFailure
|
|
1435
|
+
};
|
|
1436
|
+
activeTurn = currentTurn;
|
|
1437
|
+
let initialized = false;
|
|
1438
|
+
if (client == null) {
|
|
1439
|
+
const created = new CodexAppServerClient({
|
|
1440
|
+
executable: process.execPath,
|
|
1441
|
+
args: createCodexAppServerArgs({
|
|
1442
|
+
cliOverrides: trustedHook?.cliOverrides
|
|
1443
|
+
}),
|
|
1444
|
+
cwd: workdir2,
|
|
1445
|
+
env: process.env,
|
|
1446
|
+
onNotification: (notification) => {
|
|
1447
|
+
if (client !== created || activeTurn == null) return;
|
|
1448
|
+
const params = asRecord3(notification.params);
|
|
1449
|
+
if (notification.method === "turn/started" && params?.threadId === activeTurn.threadId && activeTurn.turnId == null) {
|
|
1450
|
+
const turnId = asRecord3(params?.turn)?.id;
|
|
1451
|
+
if (typeof turnId === "string") activeTurn.turnId = turnId;
|
|
1452
|
+
}
|
|
1453
|
+
activeTurn.handler.handle(notification);
|
|
1454
|
+
},
|
|
1455
|
+
onRequest: (request) => {
|
|
1456
|
+
const active = activeTurn;
|
|
1457
|
+
const params = asRecord3(request.params);
|
|
1458
|
+
if (client !== created || active == null || params?.threadId !== active.threadId || params?.turnId !== active.turnId || active.turnId == null) {
|
|
1459
|
+
return Promise.reject(
|
|
1460
|
+
new Error(
|
|
1461
|
+
"Codex app-server requested a tool for an inactive turn."
|
|
1462
|
+
)
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
return handleAppServerRequest({
|
|
1466
|
+
request,
|
|
1467
|
+
dynamicTools: active.dynamicTools,
|
|
1468
|
+
emit: active.options.emit,
|
|
1469
|
+
requestToolResult: active.options.turn.requestToolResult
|
|
1470
|
+
}).catch((error) => {
|
|
1471
|
+
active.fail(error);
|
|
1472
|
+
throw error;
|
|
1473
|
+
});
|
|
1474
|
+
},
|
|
1475
|
+
onStderr: (text) => {
|
|
1476
|
+
const message = text.trim();
|
|
1477
|
+
if (message.length > 0) {
|
|
1478
|
+
activeTurn?.options.turn.bridgeLog({
|
|
1479
|
+
level: "debug",
|
|
1480
|
+
subsystem: "codex.app-server.stderr",
|
|
1481
|
+
message
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
client = created;
|
|
1487
|
+
loadedConfig = processConfig;
|
|
1488
|
+
initialized = true;
|
|
820
1489
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1490
|
+
const runningClient = client;
|
|
1491
|
+
const clientFailure = runningClient.waitUntilFailure();
|
|
1492
|
+
const exitFailure = runningClient.waitUntilExit().then(({ code, signal }) => {
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
`Codex app-server exited before the turn completed (code ${code ?? "null"}, signal ${signal ?? "null"}).`
|
|
1495
|
+
);
|
|
1496
|
+
});
|
|
1497
|
+
let removeAbortListener = () => {
|
|
1498
|
+
};
|
|
1499
|
+
const abortFailure = new Promise((_, reject) => {
|
|
1500
|
+
const onAbort = () => {
|
|
1501
|
+
if (currentTurn.threadId != null && currentTurn.turnId != null) {
|
|
1502
|
+
void runningClient.request({
|
|
1503
|
+
method: "turn/interrupt",
|
|
1504
|
+
params: {
|
|
1505
|
+
threadId: currentTurn.threadId,
|
|
1506
|
+
turnId: currentTurn.turnId
|
|
1507
|
+
}
|
|
1508
|
+
}).catch(() => {
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
reject(
|
|
1512
|
+
turn.abortSignal.reason ?? new DOMException("Aborted", "AbortError")
|
|
1513
|
+
);
|
|
1514
|
+
};
|
|
1515
|
+
if (turn.abortSignal.aborted) onAbort();
|
|
1516
|
+
else {
|
|
1517
|
+
turn.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
1518
|
+
removeAbortListener = () => turn.abortSignal.removeEventListener("abort", onAbort);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
const raceWithProcess = ({ operation }) => Promise.race([
|
|
1522
|
+
operation,
|
|
1523
|
+
abortFailure,
|
|
1524
|
+
protocolFailure,
|
|
1525
|
+
clientFailure,
|
|
1526
|
+
exitFailure
|
|
1527
|
+
]);
|
|
1528
|
+
let stopSteering = () => {
|
|
1529
|
+
};
|
|
1530
|
+
const steeringStopped = new Promise((resolve) => {
|
|
1531
|
+
stopSteering = resolve;
|
|
1532
|
+
});
|
|
1533
|
+
const userMessageLoop = async () => {
|
|
1534
|
+
for await (const message of turn.experimental_userMessages) {
|
|
1535
|
+
try {
|
|
1536
|
+
const response = await Promise.race([
|
|
1537
|
+
raceWithProcess({
|
|
1538
|
+
operation: runningClient.request({
|
|
1539
|
+
method: "turn/steer",
|
|
1540
|
+
params: {
|
|
1541
|
+
threadId: currentTurn.threadId,
|
|
1542
|
+
expectedTurnId: currentTurn.turnId,
|
|
1543
|
+
clientUserMessageId: message.messageId,
|
|
1544
|
+
input: [
|
|
1545
|
+
{ type: "text", text: message.text, text_elements: [] }
|
|
1546
|
+
]
|
|
1547
|
+
}
|
|
1548
|
+
})
|
|
1549
|
+
}),
|
|
1550
|
+
steeringStopped.then(() => {
|
|
1551
|
+
throw new Error(
|
|
1552
|
+
"The Codex turn ended before accepting the user message."
|
|
1553
|
+
);
|
|
1554
|
+
})
|
|
1555
|
+
]);
|
|
1556
|
+
if (asRecord3(response)?.turnId !== currentTurn.turnId) {
|
|
1557
|
+
throw new Error(
|
|
1558
|
+
"Codex app-server turn/steer returned an unexpected turn ID."
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
message.accept();
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
message.reject(error);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
let keepClient = false;
|
|
1568
|
+
try {
|
|
1569
|
+
if (initialized) {
|
|
1570
|
+
await raceWithProcess({
|
|
1571
|
+
operation: runningClient.initialize({
|
|
1572
|
+
clientName: "ai-sdk-harness-codex",
|
|
1573
|
+
clientVersion: "1"
|
|
1574
|
+
})
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
if (start.restartThread && loadedThreadId != null) {
|
|
1578
|
+
await raceWithProcess({
|
|
1579
|
+
operation: runningClient.request({
|
|
1580
|
+
method: "thread/unsubscribe",
|
|
1581
|
+
params: { threadId: loadedThreadId }
|
|
1582
|
+
})
|
|
1583
|
+
});
|
|
1584
|
+
loadedThreadId = void 0;
|
|
1585
|
+
}
|
|
1586
|
+
if (trustedHook != null) {
|
|
1587
|
+
const hooks = await raceWithProcess({
|
|
1588
|
+
operation: runningClient.request({
|
|
1589
|
+
method: "hooks/list",
|
|
1590
|
+
params: {}
|
|
1591
|
+
})
|
|
1592
|
+
});
|
|
1593
|
+
assertTrustedApplyPatchHook({ response: hooks, hook: trustedHook });
|
|
1594
|
+
}
|
|
1595
|
+
const threadMethod = threadId == null ? "thread/start" : "thread/resume";
|
|
1596
|
+
if (loadedThreadId !== threadId || threadId == null) {
|
|
1597
|
+
const threadResponse = await raceWithProcess({
|
|
1598
|
+
operation: runningClient.request({
|
|
1599
|
+
method: threadMethod,
|
|
1600
|
+
params: threadId == null ? {
|
|
1601
|
+
...createThreadParams({
|
|
1602
|
+
start,
|
|
1603
|
+
workdir: workdir2,
|
|
1604
|
+
codexModel,
|
|
1605
|
+
codexConfig: nextConfig
|
|
1606
|
+
}),
|
|
1607
|
+
dynamicTools: dynamicTools.specs,
|
|
1608
|
+
experimentalRawEvents: true
|
|
1609
|
+
} : {
|
|
1610
|
+
threadId,
|
|
1611
|
+
...createThreadParams({
|
|
1612
|
+
start,
|
|
1613
|
+
workdir: workdir2,
|
|
1614
|
+
codexModel,
|
|
1615
|
+
codexConfig: nextConfig
|
|
1616
|
+
}),
|
|
1617
|
+
excludeTurns: true
|
|
1618
|
+
}
|
|
1619
|
+
})
|
|
1620
|
+
});
|
|
1621
|
+
assertCodexThreadPermissions({
|
|
1622
|
+
response: threadResponse,
|
|
1623
|
+
method: threadMethod
|
|
1624
|
+
});
|
|
1625
|
+
currentTurn.threadId = readNestedString({
|
|
1626
|
+
value: threadResponse,
|
|
1627
|
+
path: ["thread", "id"],
|
|
1628
|
+
method: threadMethod
|
|
1629
|
+
});
|
|
1630
|
+
loadedThreadId = currentTurn.threadId;
|
|
1631
|
+
}
|
|
1632
|
+
handler.announceThread(currentTurn.threadId);
|
|
1633
|
+
emit({ type: "stream-start" });
|
|
1634
|
+
const turnResponse = await raceWithProcess({
|
|
1635
|
+
operation: runningClient.request({
|
|
1636
|
+
method: "turn/start",
|
|
1637
|
+
params: createTurnParams({
|
|
1638
|
+
threadId: currentTurn.threadId,
|
|
1639
|
+
start,
|
|
1640
|
+
codexModel,
|
|
1641
|
+
disableEnvironments: toolPolicy.disableEnvironments
|
|
1642
|
+
})
|
|
1643
|
+
})
|
|
1644
|
+
});
|
|
1645
|
+
currentTurn.turnId = readNestedString({
|
|
1646
|
+
value: turnResponse,
|
|
1647
|
+
path: ["turn", "id"],
|
|
1648
|
+
method: "turn/start"
|
|
1649
|
+
});
|
|
1650
|
+
handler.setTurnId(currentTurn.turnId);
|
|
1651
|
+
void userMessageLoop();
|
|
1652
|
+
const result = await raceWithProcess({
|
|
1653
|
+
operation: handler.waitForCompletion()
|
|
1654
|
+
});
|
|
1655
|
+
keepClient = true;
|
|
1656
|
+
assertSuccessfulTurn(result);
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
if (turn.abortSignal.aborted && currentTurn.turnId != null) {
|
|
1659
|
+
let timer;
|
|
1660
|
+
try {
|
|
1661
|
+
keepClient = await Promise.race([
|
|
1662
|
+
handler.waitForCompletion().then(() => true),
|
|
1663
|
+
new Promise((resolve) => {
|
|
1664
|
+
timer = setTimeout(() => resolve(false), 5e3);
|
|
1665
|
+
}),
|
|
1666
|
+
clientFailure.then(
|
|
1667
|
+
() => false,
|
|
1668
|
+
() => false
|
|
1669
|
+
)
|
|
1670
|
+
]);
|
|
1671
|
+
} finally {
|
|
1672
|
+
clearTimeout(timer);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
throw error;
|
|
1676
|
+
} finally {
|
|
1677
|
+
turn.experimental_userMessages.close();
|
|
1678
|
+
stopSteering();
|
|
1679
|
+
removeAbortListener();
|
|
1680
|
+
if (activeTurn === currentTurn) activeTurn = void 0;
|
|
1681
|
+
if (!keepClient) await close();
|
|
824
1682
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1683
|
+
};
|
|
1684
|
+
return { runTurn: runTurn2, close };
|
|
1685
|
+
}
|
|
1686
|
+
function createCodexAppServerArgs({
|
|
1687
|
+
cliOverrides = []
|
|
1688
|
+
} = {}) {
|
|
1689
|
+
return [
|
|
1690
|
+
CODEX_CLI_PATH,
|
|
1691
|
+
"--config",
|
|
1692
|
+
'sandbox_mode="danger-full-access"',
|
|
1693
|
+
"--config",
|
|
1694
|
+
'approval_policy="never"',
|
|
1695
|
+
...cliOverrides.flatMap((override) => ["--config", override]),
|
|
1696
|
+
"app-server",
|
|
1697
|
+
"--stdio"
|
|
1698
|
+
];
|
|
1699
|
+
}
|
|
1700
|
+
function createThreadParams({
|
|
1701
|
+
start,
|
|
1702
|
+
workdir: workdir2,
|
|
1703
|
+
codexModel,
|
|
1704
|
+
codexConfig
|
|
1705
|
+
}) {
|
|
1706
|
+
return {
|
|
1707
|
+
...codexModel == null ? {} : { model: codexModel },
|
|
1708
|
+
cwd: workdir2,
|
|
1709
|
+
approvalPolicy: "never",
|
|
1710
|
+
sandbox: "danger-full-access",
|
|
1711
|
+
config: {
|
|
1712
|
+
...codexConfig,
|
|
1713
|
+
web_search: codexConfig.web_search ?? (start.webSearch ? "live" : "disabled")
|
|
829
1714
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function createTurnParams({
|
|
1718
|
+
threadId,
|
|
1719
|
+
start,
|
|
1720
|
+
codexModel,
|
|
1721
|
+
disableEnvironments = false
|
|
1722
|
+
}) {
|
|
1723
|
+
return {
|
|
1724
|
+
threadId,
|
|
1725
|
+
...disableEnvironments ? { environments: [] } : {},
|
|
1726
|
+
input: [{ type: "text", text: start.prompt, text_elements: [] }],
|
|
1727
|
+
approvalPolicy: "never",
|
|
1728
|
+
sandboxPolicy: {
|
|
1729
|
+
type: "externalSandbox",
|
|
1730
|
+
networkAccess: "enabled"
|
|
1731
|
+
},
|
|
1732
|
+
...codexModel == null ? {} : { model: codexModel },
|
|
1733
|
+
...start.reasoningEffort == null ? {} : { effort: start.reasoningEffort },
|
|
1734
|
+
...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? { outputSchema: start.responseFormat.schema } : {}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function assertCodexThreadPermissions({
|
|
1738
|
+
response,
|
|
1739
|
+
method
|
|
1740
|
+
}) {
|
|
1741
|
+
const value = asRecord3(response);
|
|
1742
|
+
const sandbox = asRecord3(value?.sandbox);
|
|
1743
|
+
if (value?.approvalPolicy !== "never" || sandbox?.type !== "dangerFullAccess") {
|
|
1744
|
+
throw new Error(
|
|
1745
|
+
`Codex app-server ${method} did not disable approvals and its platform sandbox.`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
function createDynamicTools(tools) {
|
|
1750
|
+
const validOriginalNames = new Set(
|
|
1751
|
+
tools.map((tool) => tool.name).filter(isValidDynamicToolName)
|
|
1752
|
+
);
|
|
1753
|
+
const usedAliases = /* @__PURE__ */ new Set();
|
|
1754
|
+
const originalNameByAlias = /* @__PURE__ */ new Map();
|
|
1755
|
+
const specs = tools.map((tool, index) => {
|
|
1756
|
+
let alias = tool.name;
|
|
1757
|
+
if (!isValidDynamicToolName(alias) || usedAliases.has(alias)) {
|
|
1758
|
+
const digest = createHash2("sha256").update(tool.name).digest("hex").slice(0, 16);
|
|
1759
|
+
const base = `ai_sdk_tool_${digest}`;
|
|
1760
|
+
alias = base;
|
|
1761
|
+
let suffix = index;
|
|
1762
|
+
while (usedAliases.has(alias) || validOriginalNames.has(alias)) {
|
|
1763
|
+
alias = `${base}_${suffix++}`;
|
|
1764
|
+
}
|
|
834
1765
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1766
|
+
usedAliases.add(alias);
|
|
1767
|
+
originalNameByAlias.set(alias, tool.name);
|
|
1768
|
+
return {
|
|
1769
|
+
type: "function",
|
|
1770
|
+
name: alias,
|
|
1771
|
+
description: tool.description ?? "",
|
|
1772
|
+
inputSchema: tool.inputSchema ?? {}
|
|
1773
|
+
};
|
|
1774
|
+
});
|
|
1775
|
+
return { specs, originalNameByAlias };
|
|
1776
|
+
}
|
|
1777
|
+
function isValidDynamicToolName(name) {
|
|
1778
|
+
return name.length > 0 && name.length <= 128 && /^[a-zA-Z0-9_-]+$/.test(name) && name !== "mcp" && !name.startsWith("mcp__");
|
|
1779
|
+
}
|
|
1780
|
+
async function handleAppServerRequest({
|
|
1781
|
+
request,
|
|
1782
|
+
dynamicTools,
|
|
1783
|
+
emit,
|
|
1784
|
+
requestToolResult
|
|
1785
|
+
}) {
|
|
1786
|
+
if (request.method !== "item/tool/call") {
|
|
1787
|
+
throw new Error(
|
|
1788
|
+
`Codex app-server requested unsupported method '${request.method}'.`
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const params = asRecord3(request.params);
|
|
1792
|
+
if (params == null || typeof params.callId !== "string" || typeof params.tool !== "string") {
|
|
1793
|
+
throw new Error("Codex app-server sent an invalid dynamic tool request.");
|
|
1794
|
+
}
|
|
1795
|
+
const originalToolName = dynamicTools.originalNameByAlias.get(params.tool);
|
|
1796
|
+
if (originalToolName == null) {
|
|
1797
|
+
throw new Error(
|
|
1798
|
+
`Codex app-server requested unknown dynamic tool '${params.tool}'.`
|
|
1799
|
+
);
|
|
838
1800
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1801
|
+
emit({
|
|
1802
|
+
type: "tool-call",
|
|
1803
|
+
toolCallId: params.callId,
|
|
1804
|
+
toolName: originalToolName,
|
|
1805
|
+
input: JSON.stringify(params.arguments ?? {}),
|
|
1806
|
+
providerExecuted: false
|
|
1807
|
+
});
|
|
1808
|
+
const result = await requestToolResult(params.callId);
|
|
1809
|
+
emit({
|
|
1810
|
+
type: "tool-result",
|
|
1811
|
+
toolCallId: params.callId,
|
|
1812
|
+
toolName: originalToolName,
|
|
1813
|
+
result: result.output ?? null,
|
|
1814
|
+
isError: result.isError === true
|
|
1815
|
+
});
|
|
1816
|
+
return {
|
|
1817
|
+
contentItems: [
|
|
1818
|
+
{ type: "inputText", text: serializeToolOutput(result.output) }
|
|
1819
|
+
],
|
|
1820
|
+
success: result.isError !== true
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function serializeToolOutput(output) {
|
|
1824
|
+
if (typeof output === "string") return output;
|
|
1825
|
+
try {
|
|
1826
|
+
return JSON.stringify(output) ?? String(output);
|
|
1827
|
+
} catch {
|
|
1828
|
+
return String(output);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function readNestedString({
|
|
1832
|
+
value,
|
|
1833
|
+
path: path2,
|
|
1834
|
+
method
|
|
1835
|
+
}) {
|
|
1836
|
+
let current = value;
|
|
1837
|
+
for (const segment of path2) current = asRecord3(current)?.[segment];
|
|
1838
|
+
if (typeof current !== "string" || current.length === 0) {
|
|
1839
|
+
throw new Error(`Codex app-server ${method} returned an invalid response.`);
|
|
1840
|
+
}
|
|
1841
|
+
return current;
|
|
1842
|
+
}
|
|
1843
|
+
function assertSuccessfulTurn(result) {
|
|
1844
|
+
if (result.status === "completed") return;
|
|
1845
|
+
if (result.status === "interrupted") {
|
|
1846
|
+
throw new DOMException("Codex turn was interrupted.", "AbortError");
|
|
1847
|
+
}
|
|
1848
|
+
throw new Error(
|
|
1849
|
+
result.error ?? `Codex turn ended with status '${result.status}'.`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
function asRecord3(value) {
|
|
1853
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
842
1854
|
}
|
|
843
1855
|
|
|
844
1856
|
// src/bridge/codex-step-tracker.ts
|
|
@@ -882,7 +1894,7 @@ function isModelStepItem(item) {
|
|
|
882
1894
|
return item.type === "reasoning" || item.type === "agent_message";
|
|
883
1895
|
}
|
|
884
1896
|
function isToolStepItem(item) {
|
|
885
|
-
return item.type === "command_execution" || item.type === "mcp_tool_call" || item.type === "web_search" || item.type === "file_change" || item.type === "todo_list";
|
|
1897
|
+
return item.type === "command_execution" || item.type === "native_tool" || item.type === "mcp_tool_call" || item.type === "dynamic_tool_call" || item.type === "web_search" || item.type === "file_change" || item.type === "todo_list";
|
|
886
1898
|
}
|
|
887
1899
|
function defaultUsage() {
|
|
888
1900
|
return {
|
|
@@ -941,6 +1953,27 @@ function createEmitStreamEvent({
|
|
|
941
1953
|
const observeStep = () => {
|
|
942
1954
|
stepTracker.observeEvent({ event, itemId: id });
|
|
943
1955
|
};
|
|
1956
|
+
if (item.type === "native_tool" && item.tool != null) {
|
|
1957
|
+
if (event.type === "item.started" && item.input != null) {
|
|
1958
|
+
send({
|
|
1959
|
+
type: "tool-call",
|
|
1960
|
+
toolCallId: id,
|
|
1961
|
+
toolName: item.tool,
|
|
1962
|
+
input: item.input,
|
|
1963
|
+
providerExecuted: true
|
|
1964
|
+
});
|
|
1965
|
+
} else if (event.type === "item.completed") {
|
|
1966
|
+
send({
|
|
1967
|
+
type: "tool-result",
|
|
1968
|
+
toolCallId: id,
|
|
1969
|
+
toolName: item.tool,
|
|
1970
|
+
result: item.result,
|
|
1971
|
+
...item.isError ? { isError: true } : {}
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
stepTracker.observeEvent({ event, itemId: `native-tool:${id}` });
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
944
1977
|
if (item.type === "agent_message" && typeof item.text === "string") {
|
|
945
1978
|
if (!textByItem.has(id)) {
|
|
946
1979
|
send({ type: "text-start", id });
|
|
@@ -1108,12 +2141,13 @@ function extractMcpToolCallResult(item) {
|
|
|
1108
2141
|
function mapUsage(usage) {
|
|
1109
2142
|
const input = usage.input_tokens ?? 0;
|
|
1110
2143
|
const cacheRead = usage.cached_input_tokens ?? 0;
|
|
2144
|
+
const cacheWrite = usage.cache_write_input_tokens ?? 0;
|
|
1111
2145
|
return {
|
|
1112
2146
|
inputTokens: {
|
|
1113
2147
|
total: input,
|
|
1114
|
-
noCache: Math.max(0, input - cacheRead),
|
|
2148
|
+
noCache: Math.max(0, input - cacheRead - cacheWrite),
|
|
1115
2149
|
cacheRead,
|
|
1116
|
-
cacheWrite
|
|
2150
|
+
cacheWrite
|
|
1117
2151
|
},
|
|
1118
2152
|
outputTokens: {
|
|
1119
2153
|
total: usage.output_tokens ?? 0,
|
|
@@ -1122,241 +2156,27 @@ function mapUsage(usage) {
|
|
|
1122
2156
|
};
|
|
1123
2157
|
}
|
|
1124
2158
|
|
|
1125
|
-
// src/bridge/tool-relay.ts
|
|
1126
|
-
import { createServer } from "http";
|
|
1127
|
-
|
|
1128
|
-
// src/bridge/tool-relay-auth.ts
|
|
1129
|
-
var ToolRelayAuthorizer = class {
|
|
1130
|
-
constructor({
|
|
1131
|
-
ttlMs = 1e4,
|
|
1132
|
-
now = Date.now
|
|
1133
|
-
} = {}) {
|
|
1134
|
-
this.authorizations = [];
|
|
1135
|
-
this.pendingRequests = [];
|
|
1136
|
-
this.ttlMs = ttlMs;
|
|
1137
|
-
this.now = now;
|
|
1138
|
-
}
|
|
1139
|
-
authorizeToolCall(call) {
|
|
1140
|
-
this.pruneExpired();
|
|
1141
|
-
const key = toolRelayCallKey(call);
|
|
1142
|
-
const pendingRequestIndex = this.pendingRequests.findIndex(
|
|
1143
|
-
(request) => request.key === key
|
|
1144
|
-
);
|
|
1145
|
-
if (pendingRequestIndex !== -1) {
|
|
1146
|
-
const [pendingRequest] = this.pendingRequests.splice(
|
|
1147
|
-
pendingRequestIndex,
|
|
1148
|
-
1
|
|
1149
|
-
);
|
|
1150
|
-
clearTimeout(pendingRequest.timeout);
|
|
1151
|
-
pendingRequest.resolve(true);
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
this.authorizations.push({
|
|
1155
|
-
key,
|
|
1156
|
-
expiresAt: this.now() + this.ttlMs
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
waitForToolCallAuthorization(call) {
|
|
1160
|
-
this.pruneExpired();
|
|
1161
|
-
const key = toolRelayCallKey(call);
|
|
1162
|
-
const authorizationIndex = this.authorizations.findIndex(
|
|
1163
|
-
(authorization) => authorization.key === key
|
|
1164
|
-
);
|
|
1165
|
-
if (authorizationIndex !== -1) {
|
|
1166
|
-
this.authorizations.splice(authorizationIndex, 1);
|
|
1167
|
-
return Promise.resolve(true);
|
|
1168
|
-
}
|
|
1169
|
-
const expiresAt = this.now() + this.ttlMs;
|
|
1170
|
-
return new Promise((resolve) => {
|
|
1171
|
-
const pendingRequest = {
|
|
1172
|
-
key,
|
|
1173
|
-
expiresAt,
|
|
1174
|
-
timeout: setTimeout(() => {
|
|
1175
|
-
const index = this.pendingRequests.indexOf(pendingRequest);
|
|
1176
|
-
if (index !== -1) this.pendingRequests.splice(index, 1);
|
|
1177
|
-
resolve(false);
|
|
1178
|
-
}, this.ttlMs),
|
|
1179
|
-
resolve
|
|
1180
|
-
};
|
|
1181
|
-
this.pendingRequests.push(pendingRequest);
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
close() {
|
|
1185
|
-
this.authorizations.length = 0;
|
|
1186
|
-
for (const pendingRequest of this.pendingRequests.splice(0)) {
|
|
1187
|
-
clearTimeout(pendingRequest.timeout);
|
|
1188
|
-
pendingRequest.resolve(false);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
pruneExpired() {
|
|
1192
|
-
const now = this.now();
|
|
1193
|
-
for (let i = this.authorizations.length - 1; i >= 0; i--) {
|
|
1194
|
-
if (this.authorizations[i].expiresAt <= now) {
|
|
1195
|
-
this.authorizations.splice(i, 1);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
for (let i = this.pendingRequests.length - 1; i >= 0; i--) {
|
|
1199
|
-
const pendingRequest = this.pendingRequests[i];
|
|
1200
|
-
if (pendingRequest.expiresAt <= now) {
|
|
1201
|
-
this.pendingRequests.splice(i, 1);
|
|
1202
|
-
clearTimeout(pendingRequest.timeout);
|
|
1203
|
-
pendingRequest.resolve(false);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
var ToolRelayPendingCalls = class {
|
|
1209
|
-
constructor() {
|
|
1210
|
-
this.calls = /* @__PURE__ */ new Map();
|
|
1211
|
-
}
|
|
1212
|
-
begin({
|
|
1213
|
-
call,
|
|
1214
|
-
run
|
|
1215
|
-
}) {
|
|
1216
|
-
const key = toolRelayCallKey(call);
|
|
1217
|
-
const existing = this.calls.get(key);
|
|
1218
|
-
if (existing) return { result: existing, isNew: false };
|
|
1219
|
-
const result = run();
|
|
1220
|
-
this.calls.set(key, result);
|
|
1221
|
-
void result.finally(() => {
|
|
1222
|
-
if (this.calls.get(key) === result) {
|
|
1223
|
-
this.calls.delete(key);
|
|
1224
|
-
}
|
|
1225
|
-
}).catch(() => {
|
|
1226
|
-
});
|
|
1227
|
-
return { result, isNew: true };
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
function toolRelayCallKey({ toolName, input }) {
|
|
1231
|
-
return `${toolName}\0${canonicalJson(input ?? {})}`;
|
|
1232
|
-
}
|
|
1233
|
-
function canonicalJson(value) {
|
|
1234
|
-
return JSON.stringify(normalizeJsonValue(value));
|
|
1235
|
-
}
|
|
1236
|
-
function normalizeJsonValue(value) {
|
|
1237
|
-
if (Array.isArray(value)) {
|
|
1238
|
-
return value.map(normalizeJsonValue);
|
|
1239
|
-
}
|
|
1240
|
-
if (value && typeof value === "object") {
|
|
1241
|
-
return Object.fromEntries(
|
|
1242
|
-
Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => [key, normalizeJsonValue(entryValue)])
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
return value;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
// src/bridge/tool-relay.ts
|
|
1249
|
-
async function startAuthorizedToolRelay({
|
|
1250
|
-
tools,
|
|
1251
|
-
emit,
|
|
1252
|
-
requestToolResult,
|
|
1253
|
-
authorizer = new ToolRelayAuthorizer()
|
|
1254
|
-
}) {
|
|
1255
|
-
const toolNames = new Set(tools.map((tool) => tool.name));
|
|
1256
|
-
const pendingCalls = new ToolRelayPendingCalls();
|
|
1257
|
-
const server = createServer(async (req, res) => {
|
|
1258
|
-
try {
|
|
1259
|
-
if (req.method !== "POST" || req.url !== "/") {
|
|
1260
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1261
|
-
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
const chunks = [];
|
|
1265
|
-
for await (const chunk of req) {
|
|
1266
|
-
chunks.push(chunk);
|
|
1267
|
-
}
|
|
1268
|
-
const body = Buffer.concat(chunks).toString("utf8");
|
|
1269
|
-
const { requestId, toolName, input } = JSON.parse(body);
|
|
1270
|
-
if (!toolNames.has(toolName)) {
|
|
1271
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1272
|
-
res.end(
|
|
1273
|
-
JSON.stringify({ error: `Tool "${toolName}" is not available` })
|
|
1274
|
-
);
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
const relayCall = { toolName, input };
|
|
1278
|
-
if (!await authorizer.waitForToolCallAuthorization(relayCall)) {
|
|
1279
|
-
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1280
|
-
res.end(JSON.stringify({ error: "unauthorized tool relay request" }));
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
const { result } = pendingCalls.begin({
|
|
1284
|
-
call: relayCall,
|
|
1285
|
-
run: async () => {
|
|
1286
|
-
emit({
|
|
1287
|
-
type: "tool-call",
|
|
1288
|
-
toolCallId: requestId,
|
|
1289
|
-
toolName,
|
|
1290
|
-
input: JSON.stringify(input ?? {}),
|
|
1291
|
-
providerExecuted: false
|
|
1292
|
-
});
|
|
1293
|
-
const toolResult = await requestToolResult(requestId);
|
|
1294
|
-
emit({
|
|
1295
|
-
type: "tool-result",
|
|
1296
|
-
toolCallId: requestId,
|
|
1297
|
-
toolName,
|
|
1298
|
-
result: toolResult.output ?? null,
|
|
1299
|
-
isError: !!toolResult.isError
|
|
1300
|
-
});
|
|
1301
|
-
return toolResult;
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
const { output } = await result;
|
|
1305
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1306
|
-
res.end(JSON.stringify({ result: output }));
|
|
1307
|
-
} catch (error) {
|
|
1308
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1309
|
-
res.end(
|
|
1310
|
-
JSON.stringify({
|
|
1311
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1312
|
-
})
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
});
|
|
1316
|
-
await new Promise(
|
|
1317
|
-
(resolve) => server.listen(0, "127.0.0.1", () => resolve())
|
|
1318
|
-
);
|
|
1319
|
-
const address = server.address();
|
|
1320
|
-
if (!address || typeof address === "string") {
|
|
1321
|
-
throw new Error("tool relay did not expose a numeric port");
|
|
1322
|
-
}
|
|
1323
|
-
return {
|
|
1324
|
-
port: address.port,
|
|
1325
|
-
close: () => {
|
|
1326
|
-
authorizer.close();
|
|
1327
|
-
closeServer(server);
|
|
1328
|
-
},
|
|
1329
|
-
authorizeToolCall: (call) => authorizer.authorizeToolCall(call)
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
function closeServer(server) {
|
|
1333
|
-
try {
|
|
1334
|
-
server.close();
|
|
1335
|
-
} catch {
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
2159
|
// src/bridge/index.ts
|
|
1340
2160
|
import { argv, env as procEnv2, stdout as stdout2 } from "process";
|
|
1341
|
-
import * as codexSdkModule from "@openai/codex-sdk";
|
|
1342
2161
|
var args = parseArgs(argv.slice(2));
|
|
1343
2162
|
var workdir = requireArg({ value: args.workdir, name: "--workdir" });
|
|
1344
2163
|
var bridgeStateDir = requireArg({
|
|
1345
2164
|
value: args.bridgeStateDir,
|
|
1346
2165
|
name: "--bridge-state-dir"
|
|
1347
2166
|
});
|
|
1348
|
-
var cliShimDir = requireArg({
|
|
1349
|
-
value: args.cliShimDir,
|
|
1350
|
-
name: "--cli-shim-dir"
|
|
1351
|
-
});
|
|
1352
2167
|
var HARNESS_CLIENT_APP = procEnv2.AI_SDK_HARNESS_CLIENT_APP;
|
|
1353
|
-
var codexSdk = codexSdkModule;
|
|
1354
2168
|
var threadState = { id: void 0 };
|
|
2169
|
+
var appServer = createCodexAppServerRuntime();
|
|
1355
2170
|
await runBridge({
|
|
1356
2171
|
bridgeType: "codex",
|
|
1357
2172
|
bridgeStateDir,
|
|
1358
2173
|
onStart: runTurn,
|
|
1359
|
-
onStop: () =>
|
|
2174
|
+
onStop: async () => {
|
|
2175
|
+
const data = threadState.id ? { threadId: threadState.id } : {};
|
|
2176
|
+
await appServer.close();
|
|
2177
|
+
return data;
|
|
2178
|
+
},
|
|
2179
|
+
onDestroy: () => appServer.close()
|
|
1360
2180
|
});
|
|
1361
2181
|
async function runTurn(start, turn) {
|
|
1362
2182
|
const emit = (msg) => turn.emit(msg);
|
|
@@ -1365,22 +2185,42 @@ async function runTurn(start, turn) {
|
|
|
1365
2185
|
} else if (typeof start.resumeThreadId === "string" && start.resumeThreadId.length > 0) {
|
|
1366
2186
|
threadState.id = start.resumeThreadId;
|
|
1367
2187
|
}
|
|
1368
|
-
|
|
1369
|
-
let
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
2188
|
+
const runtime = resolveCodexRuntime({ start });
|
|
2189
|
+
let turnUsage = defaultUsage();
|
|
2190
|
+
const stepTracker = createCodexStepTracker({ send: emit });
|
|
2191
|
+
const emitStreamEvent = createEmitStreamEvent({
|
|
2192
|
+
send: emit,
|
|
2193
|
+
stepTracker,
|
|
2194
|
+
setTurnUsage: (usage) => turnUsage = usage,
|
|
2195
|
+
setThreadId: (threadId) => threadState.id = threadId,
|
|
2196
|
+
emitWarning: turn.emitWarning,
|
|
2197
|
+
emitError: turn.emitError
|
|
2198
|
+
});
|
|
2199
|
+
try {
|
|
2200
|
+
await appServer.runTurn({
|
|
2201
|
+
start,
|
|
2202
|
+
turn,
|
|
1374
2203
|
emit,
|
|
1375
|
-
|
|
2204
|
+
workdir,
|
|
2205
|
+
threadId: threadState.id,
|
|
2206
|
+
codexModel: runtime.codexModel,
|
|
2207
|
+
codexConfig: runtime.codexConfig,
|
|
2208
|
+
stepTracker,
|
|
2209
|
+
emitStreamEvent
|
|
1376
2210
|
});
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
);
|
|
2211
|
+
} catch (error) {
|
|
2212
|
+
if (!turn.abortSignal.aborted) {
|
|
2213
|
+
turn.emitError({ error, message: "codex turn failed" });
|
|
2214
|
+
}
|
|
2215
|
+
return;
|
|
1383
2216
|
}
|
|
2217
|
+
emit({
|
|
2218
|
+
type: "finish",
|
|
2219
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
2220
|
+
totalUsage: turnUsage
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
function resolveCodexRuntime({ start }) {
|
|
1384
2224
|
const codexConfig = {
|
|
1385
2225
|
...start.codexConfig,
|
|
1386
2226
|
developer_instructions: [
|
|
@@ -1396,7 +2236,7 @@ async function runTurn(start, turn) {
|
|
|
1396
2236
|
"AI Gateway auth was selected but AI_GATEWAY_BASE_URL is missing from the Codex bridge environment."
|
|
1397
2237
|
);
|
|
1398
2238
|
}
|
|
1399
|
-
const apiBaseUrl = hasGatewayAuth ? gatewayBaseUrl : procEnv2.OPENAI_BASE_URL ?? (start.headers != null ? "https://api.openai.com/v1" : void 0);
|
|
2239
|
+
const apiBaseUrl = hasGatewayAuth ? gatewayBaseUrl : procEnv2.OPENAI_BASE_URL ?? (procEnv2.CODEX_API_KEY != null || start.headers != null ? "https://api.openai.com/v1" : void 0);
|
|
1400
2240
|
const codexModel = start.model && hasGatewayAuth && !start.model.includes("/") ? `openai/${start.model}` : start.model;
|
|
1401
2241
|
if (hasGatewayAuth && codexModel?.startsWith("openai/")) {
|
|
1402
2242
|
codexConfig.model_supports_reasoning_summaries = true;
|
|
@@ -1426,81 +2266,7 @@ async function runTurn(start, turn) {
|
|
|
1426
2266
|
if (start.mcpServers != null) {
|
|
1427
2267
|
codexConfig.mcp_servers = start.mcpServers;
|
|
1428
2268
|
}
|
|
1429
|
-
|
|
1430
|
-
const codex = new codexSdk.Codex({
|
|
1431
|
-
...procEnv2.CODEX_API_KEY ? { apiKey: procEnv2.CODEX_API_KEY } : {},
|
|
1432
|
-
...!usesConfiguredModelProvider && apiBaseUrl ? { baseUrl: apiBaseUrl } : {},
|
|
1433
|
-
env: Object.fromEntries(
|
|
1434
|
-
Object.entries(procEnv2).filter(
|
|
1435
|
-
(entry) => typeof entry[1] === "string"
|
|
1436
|
-
)
|
|
1437
|
-
),
|
|
1438
|
-
...Object.keys(codexConfig).length > 0 ? { config: codexConfig } : {}
|
|
1439
|
-
});
|
|
1440
|
-
const threadOptions = {
|
|
1441
|
-
...codexModel ? { model: codexModel } : {},
|
|
1442
|
-
sandboxMode: "danger-full-access",
|
|
1443
|
-
approvalPolicy: "never",
|
|
1444
|
-
workingDirectory: workdir,
|
|
1445
|
-
skipGitRepoCheck: true,
|
|
1446
|
-
...start.reasoningEffort ? { modelReasoningEffort: start.reasoningEffort } : {},
|
|
1447
|
-
webSearchMode: start.webSearch ? "live" : "disabled"
|
|
1448
|
-
};
|
|
1449
|
-
const thread = threadState.id ? codex.resumeThread(threadState.id, threadOptions) : codex.startThread(threadOptions);
|
|
1450
|
-
emit({ type: "stream-start" });
|
|
1451
|
-
const userMessage = start.prompt;
|
|
1452
|
-
let turnUsage;
|
|
1453
|
-
const stepTracker = createCodexStepTracker({ send: emit });
|
|
1454
|
-
const emitStreamEvent = createEmitStreamEvent({
|
|
1455
|
-
send: emit,
|
|
1456
|
-
stepTracker,
|
|
1457
|
-
setTurnUsage: (usage) => turnUsage = usage,
|
|
1458
|
-
setThreadId: (threadId) => threadState.id = threadId,
|
|
1459
|
-
emitWarning: turn.emitWarning,
|
|
1460
|
-
emitError: turn.emitError
|
|
1461
|
-
});
|
|
1462
|
-
try {
|
|
1463
|
-
const { events } = await thread.runStreamed(userMessage, {
|
|
1464
|
-
signal: turn.abortSignal,
|
|
1465
|
-
...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? { outputSchema: start.responseFormat.schema } : {}
|
|
1466
|
-
});
|
|
1467
|
-
for await (const event of events) {
|
|
1468
|
-
if (turn.abortSignal.aborted) break;
|
|
1469
|
-
if (cliShimPath && event.item?.type === "command_execution") {
|
|
1470
|
-
const relayCalls = typeof event.item.command === "string" ? parseToolRelayCommands({
|
|
1471
|
-
command: event.item.command,
|
|
1472
|
-
cliShimPath
|
|
1473
|
-
}) : void 0;
|
|
1474
|
-
if (event.type === "item.started" && relay && relayCalls) {
|
|
1475
|
-
for (const relayCall of relayCalls) {
|
|
1476
|
-
relay.authorizeToolCall(relayCall);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (relayCalls) {
|
|
1480
|
-
stepTracker.observeEvent({ event, itemId: event.item.id });
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
emitStreamEvent(event);
|
|
1485
|
-
}
|
|
1486
|
-
} catch (err) {
|
|
1487
|
-
turn.emitError({ error: err, message: "codex turn failed" });
|
|
1488
|
-
return;
|
|
1489
|
-
} finally {
|
|
1490
|
-
relay?.close();
|
|
1491
|
-
}
|
|
1492
|
-
emit({
|
|
1493
|
-
type: "finish",
|
|
1494
|
-
finishReason: { unified: "stop", raw: "stop" },
|
|
1495
|
-
totalUsage: turnUsage ?? defaultUsage()
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
async function startToolRelay({
|
|
1499
|
-
tools,
|
|
1500
|
-
emit,
|
|
1501
|
-
requestToolResult
|
|
1502
|
-
}) {
|
|
1503
|
-
return startAuthorizedToolRelay({ tools, emit, requestToolResult });
|
|
2269
|
+
return { codexConfig, codexModel };
|
|
1504
2270
|
}
|
|
1505
2271
|
function parseArgs(args2) {
|
|
1506
2272
|
const out = {};
|
|
@@ -1509,8 +2275,6 @@ function parseArgs(args2) {
|
|
|
1509
2275
|
out.workdir = args2[++i];
|
|
1510
2276
|
} else if (args2[i] === "--bridge-state-dir" && i + 1 < args2.length) {
|
|
1511
2277
|
out.bridgeStateDir = args2[++i];
|
|
1512
|
-
} else if (args2[i] === "--cli-shim-dir" && i + 1 < args2.length) {
|
|
1513
|
-
out.cliShimDir = args2[++i];
|
|
1514
2278
|
}
|
|
1515
2279
|
}
|
|
1516
2280
|
return out;
|