0sec-cli 0.17.0 → 0.21.4
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/{0sec.js → 0.js} +92 -79
- package/LICENSE +1 -1
- package/README.md +77 -37
- package/chunks/adapt-loop-IGBRQAXO.js +18 -0
- package/chunks/{adgraph-JLGA6RYI.js → adgraph-AUDQTBYA.js} +5 -5
- package/chunks/agent/skills/frameworks/entra-id.yaml +2 -2
- package/chunks/agent/skills/techniques/assumption-mining.yaml +2 -2
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +2 -2
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +1 -1
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +1 -1
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +2 -2
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +1 -1
- package/chunks/agent/skills/techniques/poc-verification.yaml +1 -1
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +20 -38
- package/chunks/{artifact-scraper-GJEZMOHF.js → artifact-scraper-LNQJUORS.js} +6 -6
- package/chunks/{assumption-mining-BVMMUZHB.js → assumption-mining-4MJTP6H5.js} +24 -26
- package/chunks/{chunk-P6WKNFWX.js → chunk-2OQOQ2FZ.js} +8 -8
- package/chunks/{chunk-5G3ZXFBW.js → chunk-2WNCI664.js} +3 -3
- package/chunks/{chunk-HDWV7PGI.js → chunk-2WSXFFJZ.js} +4 -4
- package/chunks/{chunk-DW5UWPFY.js → chunk-4VGLO2YS.js} +6 -5
- package/chunks/{chunk-SNKTC4BP.js → chunk-5T6D5BVY.js} +88 -88
- package/chunks/{chunk-OEFNRYI2.js → chunk-6GHSR47F.js} +3 -6
- package/chunks/chunk-6YNKRUMC.js +4474 -0
- package/chunks/{chunk-O462Y7P2.js → chunk-ANDKY54G.js} +22317 -19616
- package/chunks/{chunk-53G27VPS.js → chunk-AZ7L3HFD.js} +7 -2
- package/chunks/{chunk-2SANI5RH.js → chunk-BACXRTQ4.js} +691 -318
- package/chunks/{chunk-SOG2U7B3.js → chunk-CAJJRUTV.js} +4 -4
- package/chunks/{chunk-57ZENEX2.js → chunk-CGHCEN7W.js} +3 -3
- package/chunks/{chunk-VGRDNSHA.js → chunk-CL5KAUM6.js} +17 -16
- package/chunks/{chunk-QOKTUOU2.js → chunk-CXKDWFNO.js} +6 -6
- package/chunks/{chunk-RWONANDA.js → chunk-DQTNI3KY.js} +5 -5
- package/chunks/{chunk-K26SZ37E.js → chunk-E2UOY5PJ.js} +288 -100
- package/chunks/{chunk-CLHCDHP4.js → chunk-ER3SUMYJ.js} +40 -18
- package/chunks/{chunk-IORC6MQX.js → chunk-FAH6V4QM.js} +31964 -32279
- package/chunks/{chunk-6LKRLK2R.js → chunk-FHC2B7LN.js} +3 -3
- package/chunks/{chunk-4Y4KQJXE.js → chunk-FWAYJ2IV.js} +16 -16
- package/chunks/{chunk-WVTBZEQO.js → chunk-G34QP2XW.js} +8 -8
- package/chunks/{chunk-MYVT64FN.js → chunk-GHWODZMR.js} +2 -2
- package/chunks/{chunk-F3WBKITT.js → chunk-GIQKESMN.js} +10 -10
- package/chunks/{chunk-2RMLOJVB.js → chunk-GVD6SJGH.js} +2 -2
- package/chunks/{chunk-RDZYQQLW.js → chunk-HBKCGJAV.js} +7 -7
- package/chunks/{chunk-SF4KZ4O3.js → chunk-HLFS6WV2.js} +2 -2
- package/chunks/chunk-HPQZHOIU.js +67 -0
- package/chunks/{chunk-3QFDYBZQ.js → chunk-IMGWPNQM.js} +231 -1308
- package/chunks/{chunk-3MOLBTLS.js → chunk-K2PWUPUI.js} +2 -2
- package/chunks/{chunk-MMLDQR4H.js → chunk-NDJU5ZZ7.js} +14 -14
- package/chunks/{chunk-H44E2CRN.js → chunk-NOPBXZUV.js} +3 -3
- package/chunks/{chunk-HE7LCA7G.js → chunk-O6SD4A36.js} +3 -3
- package/chunks/{chunk-SAFFWQW4.js → chunk-P6YG6TEZ.js} +6 -6
- package/chunks/{chunk-KKGE5RQE.js → chunk-PC6RCKQV.js} +198 -11
- package/chunks/{chunk-LP3HHYQU.js → chunk-PD2BBBKT.js} +2 -2
- package/chunks/{chunk-BKZFDZ23.js → chunk-QCUFBVIZ.js} +3 -3
- package/chunks/{chunk-KLTNTE2Z.js → chunk-QZVG3BG4.js} +14 -14
- package/chunks/{chunk-D6S3IIPG.js → chunk-RF5QGKLR.js} +9 -9
- package/chunks/{chunk-LOHTE223.js → chunk-SBMB4OL3.js} +9 -9
- package/chunks/{chunk-IOL7D5YV.js → chunk-SQCBHFWN.js} +3 -3
- package/chunks/{chunk-A6CLR72I.js → chunk-TF46UWFU.js} +3 -3
- package/chunks/chunk-TH2LW437.js +7829 -0
- package/chunks/chunk-TRH2PJSN.js +547 -0
- package/chunks/{chunk-RJWOYEOG.js → chunk-UEX7PFSE.js} +5 -5
- package/chunks/{chunk-VQG4FT5D.js → chunk-UFHN6RIU.js} +2 -2
- package/chunks/{chunk-WU6AFRAZ.js → chunk-V36HVTUL.js} +12 -12
- package/chunks/{chunk-IR537GON.js → chunk-VCSFCHAJ.js} +2 -2
- package/chunks/{chunk-YLMN3N25.js → chunk-VIT5ALXF.js} +3 -3
- package/chunks/{chunk-UJ4IK5PO.js → chunk-VOAXLO4A.js} +10 -10
- package/chunks/{chunk-UM3ZNQIM.js → chunk-W4JOXE4N.js} +100 -84
- package/chunks/{chunk-DN25OQQA.js → chunk-WF5W75WY.js} +25 -25
- package/chunks/{chunk-QKICO43A.js → chunk-WPQLBN4S.js} +5 -5
- package/chunks/{chunk-HT6P7RY3.js → chunk-WZISK5VC.js} +37 -35
- package/chunks/{chunk-QJQKHO7G.js → chunk-Y5H6E24H.js} +3 -3
- package/chunks/{chunk-KTQDLNSR.js → chunk-YA4PUM4H.js} +41 -41
- package/chunks/{chunk-47TQJWDH.js → chunk-YHWWF4QY.js} +9 -9
- package/chunks/{chunk-2CJ776PV.js → chunk-YPOA3W4O.js} +48 -39
- package/chunks/{chunk-7DQEV5QI.js → chunk-Z3IKZX3X.js} +4 -4
- package/chunks/codex-models-TYYV4JNN.js +15 -0
- package/chunks/{commands-U3AMV5ZU.js → commands-WF5RG4I2.js} +5620 -2513
- package/chunks/corpus-v1.json +2 -2
- package/chunks/data/appsec-archetypes.json +2 -2
- package/chunks/data/chromium-archetypes.json +1 -1
- package/chunks/data/freebsd-archetypes.json +1 -1
- package/chunks/data/kernel-archetypes.json +2 -2
- package/chunks/db-U3WQZVAO.js +16 -0
- package/chunks/{disclose-A6AEIEDA.js → disclose-67GZCDBO.js} +5 -5
- package/chunks/{dist-JR67XKYO.js → dist-ACC3DZUZ.js} +11 -5
- package/chunks/{dist-GL66KMUX.js → dist-EVTN3UTT.js} +6 -6
- package/chunks/{dist-FDKALV4R.js → dist-F6X62TQK.js} +290 -327
- package/chunks/dist-KEULE5IQ.js +21914 -0
- package/chunks/dist-XZB53PS5.js +53 -0
- package/chunks/eval-runner-SA6OUGHZ.js +26 -0
- package/chunks/example-manifest.json +2 -2
- package/chunks/{exploit-agent-XP23ISN7.js → exploit-agent-36Q7XQFP.js} +4 -4
- package/chunks/{exploit-autoclimb-E6T7RRSC.js → exploit-autoclimb-J4OTSTZ6.js} +6 -6
- package/chunks/{exploit-climb-2FAUP6XU.js → exploit-climb-PYEJDIC4.js} +12 -12
- package/chunks/fix-J2KBRJDV.js +12 -0
- package/chunks/{github-issues-MJ6OYOOU.js → github-issues-IGTTWM7V.js} +7 -7
- package/chunks/harness-GDUMKGSE.js +22 -0
- package/chunks/http-conformance-U5F6GKYG.js +11 -0
- package/chunks/http-sender-I5XFFY35.js +10 -0
- package/chunks/hunt-scan-UDJSCISU.js +36 -0
- package/chunks/{identity-6ZAIIWOR.js → identity-JTIVJSV7.js} +5 -5
- package/chunks/{kernel-primitive-TENE3R7T.js → kernel-primitive-F2IXUFAX.js} +6 -6
- package/chunks/{kernel-vm-runner-4F6QSNFY.js → kernel-vm-runner-276LHIOV.js} +6 -6
- package/chunks/memsafety-scan-NHUOGTEX.js +16 -0
- package/chunks/{native-loop-NTONUDP7.js → native-loop-3BSYVDUI.js} +17 -18
- package/chunks/{npm-detectors-KB5Y5ZFX.js → npm-detectors-VIYJXKFQ.js} +6 -6
- package/chunks/npm-dynamic-discovery-5J7FT3XM.js +13 -0
- package/chunks/orchestrate-DHBFRTSI.js +59 -0
- package/chunks/{pre-recon-cve-66EB6G4M.js → pre-recon-cve-X2K2P5YQ.js} +4 -4
- package/chunks/prepare-KYHGFWCJ.js +13 -0
- package/chunks/process-PNMC36JI.js +15 -0
- package/chunks/{replay-runner-RRR4E2A7.js → replay-runner-LK2XFIVG.js} +7 -7
- package/chunks/{run-ESNN4V5W.js → run-CBGE6DRQ.js} +2401 -1810
- package/chunks/{runtime-62ZQAH7H.js → runtime-PXO5G6UV.js} +11 -11
- package/chunks/runtime-XFWJTBUO.js +12 -0
- package/chunks/{scan-stream-FHI2FYZE.js → scan-stream-BMOCPKQX.js} +7 -7
- package/chunks/{scope-BI7BF4ZY.js → scope-NJHIDDTN.js} +4 -4
- package/chunks/{session-store-BCFQYDCE.js → session-store-46WBZM22.js} +6 -6
- package/chunks/source-files-REHOM44I.js +12 -0
- package/chunks/{specdrift-WCLH6TTQ.js → specdrift-AFS54WEJ.js} +5 -5
- package/chunks/token-KRWP5JYA.js +72 -0
- package/chunks/token-util-DWTL33S3.js +8 -0
- package/chunks/variant-candidates-UH2WC2HA.js +13 -0
- package/chunks/{web-recon-prepass-DPVCBRZA.js → web-recon-prepass-SFXU44SS.js} +9 -9
- package/dashboard/assets/desktop-BT7RkC4q.js +32 -0
- package/dashboard/assets/desktop-OaBI0E0e.css +1 -0
- package/dashboard/assets/{findings-page-CACW9nw2.js → findings-page-B4rwF6fZ.js} +2 -2
- package/dashboard/assets/{format-PoISqsES.js → format-LNQSSpn6.js} +1 -1
- package/dashboard/assets/live-page-DoNwpPAO.js +1 -0
- package/dashboard/assets/{meta-tile-CcskIV_o.js → meta-tile-BMtBC-Qq.js} +1 -1
- package/dashboard/assets/operations-CMFVrXEe.css +2 -0
- package/dashboard/assets/operations-app-ER0gg7GN.js +2 -0
- package/dashboard/assets/{operations-D8nUP5_m.js → operations-e2CGYKB1.js} +2 -2
- package/dashboard/assets/{overview-page-DTGS8dPo.js → overview-page-CmeWH17A.js} +1 -1
- package/dashboard/assets/{page-header-MFVvEtZW.js → page-header-CIJyycfz.js} +1 -1
- package/dashboard/assets/scans-page-CuhzWhHx.js +1 -0
- package/dashboard/assets/{table-BYS-nIVA.js → table-BgsSBdke.js} +1 -1
- package/dashboard/assets/{tabs-DivxbhQy.js → tabs-vn6v7Bk6.js} +1 -1
- package/dashboard/desktop.html +3 -3
- package/dashboard/index.html +3 -3
- package/package.json +11 -11
- package/chunks/adapt-loop-ECQG7454.js +0 -18
- package/chunks/appsec-catalog-CBWHGTEL.js +0 -24
- package/chunks/chunk-H2FFLZNK.js +0 -3
- package/chunks/chunk-QI233I24.js +0 -333
- package/chunks/chunk-RRMJC3ZE.js +0 -105
- package/chunks/chunk-SZJCPG2I.js +0 -110
- package/chunks/chunk-WKNNZVJS.js +0 -2798
- package/chunks/cost-ledger-ZCKMBJEN.js +0 -13
- package/chunks/db-26NGQFKO.js +0 -16
- package/chunks/eval-runner-DDLE5RQ3.js +0 -27
- package/chunks/fix-IG7LYXH3.js +0 -12
- package/chunks/harness-HWOYFKFY.js +0 -22
- package/chunks/http-conformance-DU66MZIU.js +0 -11
- package/chunks/http-sender-GWH2IYEA.js +0 -10
- package/chunks/hunt-scan-XPELSPQQ.js +0 -38
- package/chunks/memsafety-scan-J7SECV5W.js +0 -16
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +0 -13
- package/chunks/orchestrate-ZUWAUWBC.js +0 -62
- package/chunks/pipeline-FCARFZU3.js +0 -14
- package/chunks/prepare-MYY743TK.js +0 -13
- package/chunks/process-3Q7QJIOZ.js +0 -13
- package/chunks/runtime-J7PLXZNM.js +0 -12
- package/chunks/source-files-PWRQR6LY.js +0 -12
- package/chunks/variant-candidates-6KM6F6MD.js +0 -13
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/desktop-CXTQonNm.js +0 -32
- package/dashboard/assets/desktop-NOekk4QS.css +0 -1
- package/dashboard/assets/live-page-DrgQqQK5.js +0 -1
- package/dashboard/assets/operations-BKE8T-vY.css +0 -2
- package/dashboard/assets/operations-app-CQQhG54n.js +0 -2
- package/dashboard/assets/scans-page-ChVtq8Us.js +0 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire as
|
|
3
|
-
const require =
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
VERSION,
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
cloudStateDir
|
|
7
|
+
} from "./chunk-PC6RCKQV.js";
|
|
8
8
|
|
|
9
9
|
// packages/core/dist/agent/feature-presets.js
|
|
10
10
|
var FP_MOAT_FLAGS = [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
11
|
+
"ZERO_FEATURE_REACHABILITY_GATE",
|
|
12
|
+
"ZERO_FEATURE_MULTIMODAL",
|
|
13
|
+
"ZERO_FEATURE_PUBLISHABILITY_GATE",
|
|
14
|
+
"ZERO_FEATURE_POV_GATE",
|
|
15
|
+
"ZERO_FEATURE_POC_GEN_STATIC",
|
|
16
|
+
"ZERO_FEATURE_CONSENSUS_VERIFY"
|
|
17
17
|
];
|
|
18
18
|
var FEATURE_PRESETS = Object.freeze({
|
|
19
19
|
"fp-moat": FP_MOAT_FLAGS
|
|
@@ -41,7 +41,7 @@ function applyFeaturePreset(preset, env2 = process.env) {
|
|
|
41
41
|
return { preset, applied, preserved };
|
|
42
42
|
}
|
|
43
43
|
function applyFeaturePresetFromEnv(env2 = process.env) {
|
|
44
|
-
const raw = env2["
|
|
44
|
+
const raw = env2["ZERO_FEATURE_PRESET"];
|
|
45
45
|
if (!raw)
|
|
46
46
|
return void 0;
|
|
47
47
|
const preset = resolveFeaturePreset(raw);
|
|
@@ -63,73 +63,73 @@ var features = {
|
|
|
63
63
|
*
|
|
64
64
|
* Turn count is also a cost PROXY, and cost is already bounded directly by
|
|
65
65
|
* the token budget, so this never was the control that kept spend in check.
|
|
66
|
-
* Enable with `
|
|
66
|
+
* Enable with `ZERO_FEATURE_EARLY_STOP=1` for benchmark or A/B runs where a
|
|
67
67
|
* fixed turn budget per attempt is the point.
|
|
68
68
|
*/
|
|
69
69
|
get earlyStopRetry() {
|
|
70
|
-
return env("
|
|
70
|
+
return env("ZERO_FEATURE_EARLY_STOP", false);
|
|
71
71
|
},
|
|
72
72
|
/** Detect A-A-A and A-B-A-B loop patterns, inject warning */
|
|
73
73
|
get loopDetection() {
|
|
74
|
-
return env("
|
|
74
|
+
return env("ZERO_FEATURE_LOOP_DETECTION", true);
|
|
75
75
|
},
|
|
76
76
|
/** Compress middle messages when context exceeds 30k tokens */
|
|
77
77
|
get contextCompaction() {
|
|
78
|
-
return env("
|
|
78
|
+
return env("ZERO_FEATURE_CONTEXT_COMPACTION", true);
|
|
79
79
|
},
|
|
80
80
|
/**
|
|
81
81
|
* Re-send the opaque, model-bound Responses output item array on the next
|
|
82
82
|
* turn. Default ON; set to 0 only for matched retained-reasoning A/B runs.
|
|
83
83
|
*/
|
|
84
84
|
get retainedReasoning() {
|
|
85
|
-
return env("
|
|
85
|
+
return env("ZERO_FEATURE_RETAINED_REASONING", true);
|
|
86
86
|
},
|
|
87
87
|
/** Exploit script templates in shell prompt (blind SQLi, SSTI, auth chain) */
|
|
88
88
|
get scriptTemplates() {
|
|
89
|
-
return env("
|
|
89
|
+
return env("ZERO_FEATURE_SCRIPT_TEMPLATES", true);
|
|
90
90
|
},
|
|
91
91
|
/** Dynamic vulnerability playbooks injected after recon phase */
|
|
92
92
|
get dynamicPlaybooks() {
|
|
93
|
-
return env("
|
|
93
|
+
return env("ZERO_FEATURE_DYNAMIC_PLAYBOOKS", false);
|
|
94
94
|
},
|
|
95
95
|
/** Just-in-time atomic DO/DON'T rules injected on a matching tool action */
|
|
96
96
|
get ruleInjection() {
|
|
97
|
-
return env("
|
|
97
|
+
return env("ZERO_FEATURE_RULE_INJECTION", false);
|
|
98
98
|
},
|
|
99
99
|
/** Agent writes plan/creds to disk, injected at reflection checkpoints */
|
|
100
100
|
get externalMemory() {
|
|
101
|
-
return env("
|
|
101
|
+
return env("ZERO_FEATURE_EXTERNAL_MEMORY", false);
|
|
102
102
|
},
|
|
103
103
|
/** Inject prior attempt findings when retrying (LLM-summarized progress handoff) */
|
|
104
104
|
get progressHandoff() {
|
|
105
|
-
return env("
|
|
105
|
+
return env("ZERO_FEATURE_PROGRESS_HANDOFF", true);
|
|
106
106
|
},
|
|
107
107
|
/** Allow the agent to search the web for CVE details, docs, and technique references */
|
|
108
108
|
get webSearch() {
|
|
109
|
-
return env("
|
|
109
|
+
return env("ZERO_FEATURE_WEB_SEARCH", false);
|
|
110
110
|
},
|
|
111
111
|
/** Interactive PTY sessions for exploits requiring interactivity (reverse shells, DB clients, SSH) */
|
|
112
112
|
get ptySession() {
|
|
113
|
-
return env("
|
|
113
|
+
return env("ZERO_FEATURE_PTY_SESSION", false);
|
|
114
114
|
},
|
|
115
115
|
/**
|
|
116
116
|
* Persistent, COMPUTE-ONLY Python REPL (`python_exec`, Phase-0). A framed
|
|
117
117
|
* python3 kernel keeps state across calls for payload/parse/crypto/encode
|
|
118
118
|
* work; networking is blocked at the socket source whenever an engagement is
|
|
119
|
-
* active. Default OFF — opt in via
|
|
119
|
+
* active. Default OFF — opt in via ZERO_FEATURE_PYTHON_EXEC=1. Getter so
|
|
120
120
|
* the CLI `--features` flag (set after this module is imported) is honored at
|
|
121
121
|
* tool-dispatch time.
|
|
122
122
|
*/
|
|
123
123
|
get pythonExec() {
|
|
124
|
-
return env("
|
|
124
|
+
return env("ZERO_FEATURE_PYTHON_EXEC", false);
|
|
125
125
|
},
|
|
126
126
|
/**
|
|
127
127
|
* Expose the path-confined `analyze_binary` bridge to 0verse. Default OFF:
|
|
128
128
|
* a model may request a long-running binary analysis only after an operator
|
|
129
|
-
* opts in with
|
|
129
|
+
* opts in with ZERO_FEATURE_ZEROVERSE=1.
|
|
130
130
|
*/
|
|
131
131
|
get zeroverse() {
|
|
132
|
-
return env("
|
|
132
|
+
return env("ZERO_FEATURE_ZEROVERSE", false);
|
|
133
133
|
},
|
|
134
134
|
/**
|
|
135
135
|
* EGATS specialist routing (#557, HPTSA-inspired). When ON, an EGATS branch
|
|
@@ -146,22 +146,22 @@ var features = {
|
|
|
146
146
|
* benchmark harness. Implemented as a getter so the CLI `--features` flag
|
|
147
147
|
* (which sets the env var inside the command action, AFTER this module has
|
|
148
148
|
* been imported) is honored at routing time. Enable via
|
|
149
|
-
*
|
|
149
|
+
* ZERO_FEATURE_SPECIALIST_ROUTING=1.
|
|
150
150
|
*/
|
|
151
151
|
get specialistRouting() {
|
|
152
|
-
return env("
|
|
152
|
+
return env("ZERO_FEATURE_SPECIALIST_ROUTING", false);
|
|
153
153
|
},
|
|
154
154
|
/** Self-consistency voting: run the structured verify pipeline N times and take the majority vote */
|
|
155
155
|
get selfConsistencyVerify() {
|
|
156
|
-
return env("
|
|
156
|
+
return env("ZERO_FEATURE_CONSENSUS_VERIFY", false);
|
|
157
157
|
},
|
|
158
158
|
/** Multi-modal agreement: cross-validate findings against foxguard (Rust pattern scanner) */
|
|
159
159
|
get multiModalAgreement() {
|
|
160
|
-
return env("
|
|
160
|
+
return env("ZERO_FEATURE_MULTIMODAL", false);
|
|
161
161
|
},
|
|
162
162
|
/** Reachability gate: suppress findings whose sink is not reachable from an application entry point */
|
|
163
163
|
get reachabilityGate() {
|
|
164
|
-
return env("
|
|
164
|
+
return env("ZERO_FEATURE_REACHABILITY_GATE", false);
|
|
165
165
|
},
|
|
166
166
|
/**
|
|
167
167
|
* Publishability / in-scope gate (issue #537 / #539). Decides
|
|
@@ -173,14 +173,14 @@ var features = {
|
|
|
173
173
|
*
|
|
174
174
|
* Default OFF: this gate can suppress reproducible findings, so it must be
|
|
175
175
|
* explicitly opted into before any A/B claim. Disable/enable via
|
|
176
|
-
*
|
|
176
|
+
* ZERO_FEATURE_PUBLISHABILITY_GATE.
|
|
177
177
|
*/
|
|
178
178
|
get publishabilityGate() {
|
|
179
|
-
return env("
|
|
179
|
+
return env("ZERO_FEATURE_PUBLISHABILITY_GATE", false);
|
|
180
180
|
},
|
|
181
181
|
/** PoV gate: require a working, executable PoC per finding or downgrade to info */
|
|
182
182
|
get povGate() {
|
|
183
|
-
return env("
|
|
183
|
+
return env("ZERO_FEATURE_POV_GATE", false);
|
|
184
184
|
},
|
|
185
185
|
/**
|
|
186
186
|
* Intra-scan semantic dedupe post-pass (anchored incremental LLM
|
|
@@ -188,10 +188,10 @@ var features = {
|
|
|
188
188
|
* Marks duplicates with a canonical mapping + cluster reason instead of
|
|
189
189
|
* dropping them. Default OFF: it spends an LLM call per ≤50-finding batch
|
|
190
190
|
* after the scan, so it must be explicitly opted into before any A/B
|
|
191
|
-
* claim. Toggle via
|
|
191
|
+
* claim. Toggle via ZERO_FEATURE_SEMANTIC_DEDUPE.
|
|
192
192
|
*/
|
|
193
193
|
get semanticDedupe() {
|
|
194
|
-
return env("
|
|
194
|
+
return env("ZERO_FEATURE_SEMANTIC_DEDUPE", false);
|
|
195
195
|
},
|
|
196
196
|
/**
|
|
197
197
|
* Finding-specific remediation written by the model
|
|
@@ -207,33 +207,17 @@ var features = {
|
|
|
207
207
|
* degrade cost, never correctness.
|
|
208
208
|
*/
|
|
209
209
|
get llmRemediation() {
|
|
210
|
-
return env("
|
|
211
|
-
},
|
|
212
|
-
/**
|
|
213
|
-
* Per-finding impact assessment (`assessImpact`, `triage/impact-assessment.ts`)
|
|
214
|
-
* written by the model: reachability tier, weaponizability, blast radius,
|
|
215
|
-
* business-impact tier. Default OFF — one extra LLM call per non-false-positive
|
|
216
|
-
* finding at report time.
|
|
217
|
-
*
|
|
218
|
-
* When on, the assessment feeds three things it is otherwise absent from:
|
|
219
|
-
* a real CVSS exploitability vector (AV/PR/UI from the reachability tier
|
|
220
|
-
* rather than the AV:N/severity-floor guess), the advisory's Impact +
|
|
221
|
-
* attack-prerequisites section, and the vendor-notification impact line. When
|
|
222
|
-
* off, all three fall back to today's category/severity heuristics — so this
|
|
223
|
-
* flag strictly adds fidelity, never changes the no-assessment output.
|
|
224
|
-
*/
|
|
225
|
-
get impactAssessment() {
|
|
226
|
-
return env("0SEC_FEATURE_IMPACT_ASSESSMENT", false);
|
|
210
|
+
return env("ZERO_FEATURE_LLM_REMEDIATION", false);
|
|
227
211
|
},
|
|
228
212
|
/**
|
|
229
213
|
* Incremental finding ranking post-pass (decimal-insertion between ranked
|
|
230
214
|
* anchors, `triage/incremental-rank.ts`). Orders the report by comparative
|
|
231
215
|
* promise (exploitability × impact × evidence strength). Default OFF: it
|
|
232
216
|
* spends an LLM call per ≤50-finding batch; opt in before any A/B claim.
|
|
233
|
-
* Toggle via
|
|
217
|
+
* Toggle via ZERO_FEATURE_INCREMENTAL_RANK.
|
|
234
218
|
*/
|
|
235
219
|
get incrementalRank() {
|
|
236
|
-
return env("
|
|
220
|
+
return env("ZERO_FEATURE_INCREMENTAL_RANK", false);
|
|
237
221
|
},
|
|
238
222
|
/**
|
|
239
223
|
* Static-finding PoC generation (#666 / EPIC #674 Part A). For findings that
|
|
@@ -247,10 +231,10 @@ var features = {
|
|
|
247
231
|
*
|
|
248
232
|
* Default OFF: it spends LLM + execution budget per static finding and must
|
|
249
233
|
* be explicitly opted into before any A/B claim (A/B-able via the #656
|
|
250
|
-
* harness). Toggle via
|
|
234
|
+
* harness). Toggle via ZERO_FEATURE_POC_GEN_STATIC.
|
|
251
235
|
*/
|
|
252
236
|
get pocGenStatic() {
|
|
253
|
-
return env("
|
|
237
|
+
return env("ZERO_FEATURE_POC_GEN_STATIC", false);
|
|
254
238
|
},
|
|
255
239
|
/**
|
|
256
240
|
* Inline validation / validate-on-save (#554). When ON, the native attack
|
|
@@ -268,10 +252,10 @@ var features = {
|
|
|
268
252
|
* cost_per_flag claim. Implemented as a getter so the CLI `--features` flag
|
|
269
253
|
* (which sets the env var inside the command action, AFTER this module is
|
|
270
254
|
* imported) is honored at loop time. Enable via
|
|
271
|
-
*
|
|
255
|
+
* ZERO_FEATURE_INLINE_VALIDATION=1.
|
|
272
256
|
*/
|
|
273
257
|
get inlineValidation() {
|
|
274
|
-
return env("
|
|
258
|
+
return env("ZERO_FEATURE_INLINE_VALIDATION", false);
|
|
275
259
|
},
|
|
276
260
|
/**
|
|
277
261
|
* WordPress plugin/theme fingerprinter + OSV CVE lookup.
|
|
@@ -286,7 +270,7 @@ var features = {
|
|
|
286
270
|
* still honored at tool-dispatch time.
|
|
287
271
|
*/
|
|
288
272
|
get wpFingerprint() {
|
|
289
|
-
return env("
|
|
273
|
+
return env("ZERO_FEATURE_WP_FINGERPRINT", true);
|
|
290
274
|
},
|
|
291
275
|
/**
|
|
292
276
|
* MongoDB ObjectID forge tool. Exposes the `mongo_objectid` tool to the
|
|
@@ -296,23 +280,23 @@ var features = {
|
|
|
296
280
|
*
|
|
297
281
|
* Default ON — this is a pure-computation utility with no network or
|
|
298
282
|
* filesystem side effects, so there's no reason to gate it off. Disable
|
|
299
|
-
* via
|
|
283
|
+
* via ZERO_FEATURE_MONGO_OBJECTID_FORGE=0 or `--no-mongo-objectid-forge`
|
|
300
284
|
* for ablation. Implemented as a getter so the CLI `--features` flag
|
|
301
285
|
* (which sets the env var inside the command action, AFTER this module
|
|
302
286
|
* has been imported) is still honored at tool-dispatch time. Matches
|
|
303
287
|
* the wpFingerprint pattern above. See packages/core/src/agent/objectid-forge.ts.
|
|
304
288
|
*/
|
|
305
289
|
get mongoObjectIdForge() {
|
|
306
|
-
return env("
|
|
290
|
+
return env("ZERO_FEATURE_MONGO_OBJECTID_FORGE", true);
|
|
307
291
|
},
|
|
308
292
|
/**
|
|
309
|
-
* Live cloud-surface testing (
|
|
293
|
+
* Live cloud-surface testing (0#925). Exposes `cloud_s3_probe` and
|
|
310
294
|
* `cloud_validate_credentials` to the attack agent so it can test S3 buckets
|
|
311
295
|
* for public access + orphaned-bucket takeover and safely validate harvested
|
|
312
296
|
* AWS credentials (read-only). All probes are anonymous or read/verify-only —
|
|
313
297
|
* no writes, no data exfiltration beyond minimal proof.
|
|
314
298
|
*
|
|
315
|
-
* Default OFF (opt-in via
|
|
299
|
+
* Default OFF (opt-in via ZERO_FEATURE_CLOUD_SURFACE=1). Probing a target
|
|
316
300
|
* org's bucket-name space or validating its harvested credentials is recon
|
|
317
301
|
* AGAINST THAT ORG, so it is deny-by-default at two layers: this enablement
|
|
318
302
|
* flag, AND an engagement-scope check in the tool handlers (a configured
|
|
@@ -323,7 +307,7 @@ var features = {
|
|
|
323
307
|
* mongoObjectIdForge pattern above. See packages/core/src/agent/cloud-surface.ts.
|
|
324
308
|
*/
|
|
325
309
|
get cloudSurface() {
|
|
326
|
-
return env("
|
|
310
|
+
return env("ZERO_FEATURE_CLOUD_SURFACE", false);
|
|
327
311
|
},
|
|
328
312
|
/**
|
|
329
313
|
* #978 (ADR-060) — agent fan-out. When ON, the agent gets the `start_scan`
|
|
@@ -331,11 +315,11 @@ var features = {
|
|
|
331
315
|
* that run independently and report up the scan tree — the recursive
|
|
332
316
|
* sub-agent orchestration. Default OFF: fan-out multiplies scans/cost, so it
|
|
333
317
|
* stays opt-in even though the orchestrator enforces budget + a tree-level
|
|
334
|
-
* cap (max children/depth). Enable with
|
|
318
|
+
* cap (max children/depth). Enable with ZERO_FEATURE_AGENT_FANOUT=1.
|
|
335
319
|
* Getter so the CLI `--features` flag is honored at dispatch time.
|
|
336
320
|
*/
|
|
337
321
|
get agentFanout() {
|
|
338
|
-
return env("
|
|
322
|
+
return env("ZERO_FEATURE_AGENT_FANOUT", false);
|
|
339
323
|
},
|
|
340
324
|
// ── Phase-2 offensive-engine feature flags (dev-live-engine-recovery) ──
|
|
341
325
|
// Each gates a tool that RUNS/BUILDS untrusted code or WEAPONIZES. They are
|
|
@@ -347,21 +331,21 @@ var features = {
|
|
|
347
331
|
/**
|
|
348
332
|
* `memsafety_fuzz` — clones/builds/fuzzes a source tree (sanitizer builds +
|
|
349
333
|
* a fuzz harness), executing attacker-adjacent build scripts and native
|
|
350
|
-
* fuzz targets. Default OFF; opt in via
|
|
334
|
+
* fuzz targets. Default OFF; opt in via ZERO_FEATURE_MEMSAFETY=1. Building
|
|
351
335
|
* and running an untrusted tree is code execution, so it is deny-by-default
|
|
352
336
|
* behind this flag AND an engagement scope.
|
|
353
337
|
*/
|
|
354
338
|
get memsafetyFuzz() {
|
|
355
|
-
return env("
|
|
339
|
+
return env("ZERO_FEATURE_MEMSAFETY", false);
|
|
356
340
|
},
|
|
357
341
|
/**
|
|
358
342
|
* `npm_dynamic_discovery` — installs and RUNS untrusted npm packages under
|
|
359
343
|
* instrumentation to observe malicious install/runtime behaviour. Executing
|
|
360
344
|
* arbitrary package code is the whole point, so it is deny-by-default behind
|
|
361
|
-
* this flag AND an engagement scope. Opt in via
|
|
345
|
+
* this flag AND an engagement scope. Opt in via ZERO_FEATURE_NPM_DISCOVERY=1.
|
|
362
346
|
*/
|
|
363
347
|
get npmDynamicDiscovery() {
|
|
364
|
-
return env("
|
|
348
|
+
return env("ZERO_FEATURE_NPM_DISCOVERY", false);
|
|
365
349
|
},
|
|
366
350
|
/**
|
|
367
351
|
* `weaponize_kernel` — the kernel-exploit weaponization ladder. It only ever
|
|
@@ -369,19 +353,19 @@ var features = {
|
|
|
369
353
|
* present. Highest-caution capability: deny-by-default behind this flag AND
|
|
370
354
|
* an engagement scope AND a runtime artifact-presence check (the handler
|
|
371
355
|
* refuses when the kernel-VM assets are absent). Opt in via
|
|
372
|
-
*
|
|
356
|
+
* ZERO_FEATURE_KERNEL_WEAPONIZE=1.
|
|
373
357
|
*/
|
|
374
358
|
get kernelWeaponize() {
|
|
375
|
-
return env("
|
|
359
|
+
return env("ZERO_FEATURE_KERNEL_WEAPONIZE", false);
|
|
376
360
|
},
|
|
377
361
|
/**
|
|
378
362
|
* `cve_adapt` — adapts a public CVE PoC to the target and RUNS it to confirm
|
|
379
363
|
* exploitability. Running an adapted exploit is code execution against the
|
|
380
364
|
* target, so it is deny-by-default behind this flag AND an engagement scope.
|
|
381
|
-
* Opt in via
|
|
365
|
+
* Opt in via ZERO_FEATURE_CVE_ADAPT=1.
|
|
382
366
|
*/
|
|
383
367
|
get cveAdapt() {
|
|
384
|
-
return env("
|
|
368
|
+
return env("ZERO_FEATURE_CVE_ADAPT", false);
|
|
385
369
|
},
|
|
386
370
|
/**
|
|
387
371
|
* Anti-honeypot flag-shape validator. When the agent calls the `done`
|
|
@@ -392,7 +376,7 @@ var features = {
|
|
|
392
376
|
*
|
|
393
377
|
* Default ON because legitimate flags pass the shape check trivially
|
|
394
378
|
* and the false-positive rate on real flags should be near zero. Turn
|
|
395
|
-
* off via `
|
|
379
|
+
* off via `ZERO_FEATURE_DECOY_DETECTION=0` or the CLI flag
|
|
396
380
|
* `--no-decoy-detection` for ablation/testing.
|
|
397
381
|
*
|
|
398
382
|
* Implemented as a getter so the CLI flag (which flips the env var
|
|
@@ -402,7 +386,7 @@ var features = {
|
|
|
402
386
|
* packages/core/src/agent/flag-validator.ts.
|
|
403
387
|
*/
|
|
404
388
|
get decoyDetection() {
|
|
405
|
-
return env("
|
|
389
|
+
return env("ZERO_FEATURE_DECOY_DETECTION", true);
|
|
406
390
|
},
|
|
407
391
|
// ── Always-on triage filters (default ON, ablatable for A/B testing) ──
|
|
408
392
|
/**
|
|
@@ -411,13 +395,13 @@ var features = {
|
|
|
411
395
|
* sink names and rejects findings that look like "the function did its job".
|
|
412
396
|
*
|
|
413
397
|
* Default ON because that's the existing v0.6.0 behavior. Can be disabled
|
|
414
|
-
* via
|
|
398
|
+
* via ZERO_FEATURE_HOLDING_IT_WRONG=0 to test whether this filter is
|
|
415
399
|
* suppressing real signal — the ceiling-analysis from 2026-04-06 identified
|
|
416
400
|
* this as the strongest candidate for the unexplained XBOW finding-density
|
|
417
401
|
* collapse from 14 → 4 between `features=none` and `features=all`.
|
|
418
402
|
*/
|
|
419
403
|
get holdingItWrong() {
|
|
420
|
-
return env("
|
|
404
|
+
return env("ZERO_FEATURE_HOLDING_IT_WRONG", true);
|
|
421
405
|
},
|
|
422
406
|
/**
|
|
423
407
|
* `evidence_completeness <= 0.5` reject (`packages/core/src/agentic-scanner.ts:591`).
|
|
@@ -425,10 +409,10 @@ var features = {
|
|
|
425
409
|
* gather enough cross-source evidence (request + response + analysis + ...).
|
|
426
410
|
*
|
|
427
411
|
* Default ON because that's the existing v0.6.0 behavior. Can be disabled
|
|
428
|
-
* via
|
|
412
|
+
* via ZERO_FEATURE_EVIDENCE_GATE=0 for ablation.
|
|
429
413
|
*/
|
|
430
414
|
get evidenceGate() {
|
|
431
|
-
return env("
|
|
415
|
+
return env("ZERO_FEATURE_EVIDENCE_GATE", true);
|
|
432
416
|
},
|
|
433
417
|
/**
|
|
434
418
|
* Learned per-finding triage router (`packages/core/src/triage/learned-router.ts`).
|
|
@@ -439,17 +423,17 @@ var features = {
|
|
|
439
423
|
* the scan's slice type (xbow-wb, xbow-bb, npm).
|
|
440
424
|
*
|
|
441
425
|
* Default OFF until the router is validated via A/B testing on xbow-bench
|
|
442
|
-
* and npm-bench. See
|
|
426
|
+
* and npm-bench. See 0#113 for the design doc.
|
|
443
427
|
*/
|
|
444
428
|
get learnedRouter() {
|
|
445
|
-
return env("
|
|
429
|
+
return env("ZERO_FEATURE_LEARNED_ROUTER", false);
|
|
446
430
|
},
|
|
447
431
|
/**
|
|
448
432
|
* Dynamic per-finding triage routing (`packages/core/src/triage/router/`).
|
|
449
433
|
* When enabled, every finding is sent through a `RouterModel` that
|
|
450
434
|
* decides which subset of the 11 triage layers to invoke for that
|
|
451
435
|
* specific finding. v0 ships an explicit-rule router encoded from the
|
|
452
|
-
*
|
|
436
|
+
* 0#72 per-profile ablation; a learned classifier replaces the
|
|
453
437
|
* rules in a follow-up PR without touching the dispatch site.
|
|
454
438
|
*
|
|
455
439
|
* Distinct from `learnedRouter` above: `learnedRouter` is the XGBoost
|
|
@@ -458,25 +442,25 @@ var features = {
|
|
|
458
442
|
* the dispatch router gates which layers run AFTER the TP/FP score
|
|
459
443
|
* model has spoken.
|
|
460
444
|
*
|
|
461
|
-
* Default OFF — opt in via
|
|
462
|
-
*
|
|
445
|
+
* Default OFF — opt in via ZERO_FEATURE_DYNAMIC_TRIAGE=1. See
|
|
446
|
+
* 0#113 for the design doc and 0#67 for the joint paper plan.
|
|
463
447
|
*/
|
|
464
448
|
get dynamicTriageRouting() {
|
|
465
|
-
return env("
|
|
449
|
+
return env("ZERO_FEATURE_DYNAMIC_TRIAGE", false);
|
|
466
450
|
},
|
|
467
451
|
/**
|
|
468
452
|
* Opt-in cloud-sink webhook integration (`packages/core/src/cloud-sink.ts`).
|
|
469
|
-
* When enabled AND the user has set
|
|
453
|
+
* When enabled AND the user has set ZERO_CLOUD_SINK + ZERO_CLOUD_SCAN_ID,
|
|
470
454
|
* every finding and the final scan report are POSTed to the configured
|
|
471
455
|
* remote endpoint in real time.
|
|
472
456
|
*
|
|
473
457
|
* Default ON so the env-var trio is sufficient to enable streaming, but the
|
|
474
458
|
* flag exists so operators can force-disable the integration in environments
|
|
475
459
|
* where outbound HTTP from the scanner is not desired (e.g. air-gapped CI).
|
|
476
|
-
* Disable via
|
|
460
|
+
* Disable via ZERO_FEATURE_CLOUD_SINK=0.
|
|
477
461
|
*/
|
|
478
462
|
get cloudSink() {
|
|
479
|
-
return env("
|
|
463
|
+
return env("ZERO_FEATURE_CLOUD_SINK", true);
|
|
480
464
|
},
|
|
481
465
|
/**
|
|
482
466
|
* Pre-recon CVE check (`packages/core/src/pre-recon-cve.ts`).
|
|
@@ -487,10 +471,10 @@ var features = {
|
|
|
487
471
|
* where the agent has source access but no concrete leads.
|
|
488
472
|
*
|
|
489
473
|
* Default ON in white-box mode (no-op in black-box). Disable via
|
|
490
|
-
*
|
|
474
|
+
* ZERO_FEATURE_PRE_RECON_CVE=0 for ablation.
|
|
491
475
|
*/
|
|
492
476
|
get preReconCve() {
|
|
493
|
-
return env("
|
|
477
|
+
return env("ZERO_FEATURE_PRE_RECON_CVE", true);
|
|
494
478
|
},
|
|
495
479
|
/**
|
|
496
480
|
* Deterministic web-recon pre-pass (`packages/core/src/stages/web-recon-prepass.ts`).
|
|
@@ -501,25 +485,25 @@ var features = {
|
|
|
501
485
|
* checks. It EMITS findings directly for what it can prove and injects a
|
|
502
486
|
* "pursue these leads" block into the system prompt for what it can only hint.
|
|
503
487
|
*
|
|
504
|
-
* Default ON (no-op in non-web modes). Gated behind
|
|
488
|
+
* Default ON (no-op in non-web modes). Gated behind ZERO_FEATURE_WEB_RECON
|
|
505
489
|
* so it can be disabled for ablation or offline runs. Implemented as a getter
|
|
506
490
|
* so the CLI `--features` flag (which sets the env var inside the command
|
|
507
491
|
* action, AFTER this module has been imported) is honored at stage time.
|
|
508
492
|
*/
|
|
509
493
|
get webRecon() {
|
|
510
|
-
return env("
|
|
494
|
+
return env("ZERO_FEATURE_WEB_RECON", true);
|
|
511
495
|
},
|
|
512
496
|
/**
|
|
513
497
|
* Best-effort target-history preflight for source review. When a local repo
|
|
514
|
-
* path is known,
|
|
498
|
+
* path is known, 0 infers repository/package/product hints, queries live
|
|
515
499
|
* prior-vulnerability intel, and injects a compact audit-graph summary into
|
|
516
500
|
* the review prompt before the agent starts.
|
|
517
501
|
*
|
|
518
502
|
* Default ON for white-box/source-review modes. Disable via
|
|
519
|
-
*
|
|
503
|
+
* ZERO_FEATURE_TARGET_HISTORY_PRESEED=0 for offline or ablation runs.
|
|
520
504
|
*/
|
|
521
505
|
get targetHistoryPreseed() {
|
|
522
|
-
return env("
|
|
506
|
+
return env("ZERO_FEATURE_TARGET_HISTORY_PRESEED", true);
|
|
523
507
|
},
|
|
524
508
|
/**
|
|
525
509
|
* Preserve credential / exploit-bearing messages verbatim during
|
|
@@ -534,15 +518,15 @@ var features = {
|
|
|
534
518
|
* (a handful of extra messages preserved verbatim in the user
|
|
535
519
|
* compaction-summary block) is small. BoxPwnr-inspired: see
|
|
536
520
|
* `src/boxpwnr/solvers/single_loop_compactation.py` in 0ca/BoxPwnr,
|
|
537
|
-
* and
|
|
521
|
+
* and 0#229 for the design discussion.
|
|
538
522
|
*
|
|
539
523
|
* Implemented as a getter so the CLI `--features` flag — which sets
|
|
540
524
|
* the env var inside the command action AFTER this module is imported
|
|
541
525
|
* — is still honored at compaction time. Disable via
|
|
542
|
-
*
|
|
526
|
+
* ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES=0 for ablation.
|
|
543
527
|
*/
|
|
544
528
|
get preserveCriticalMessages() {
|
|
545
|
-
return env("
|
|
529
|
+
return env("ZERO_FEATURE_PRESERVE_CRITICAL_MESSAGES", true);
|
|
546
530
|
},
|
|
547
531
|
/**
|
|
548
532
|
* Two-stage budget-warning injection in the agent loop (#408).
|
|
@@ -558,14 +542,14 @@ var features = {
|
|
|
558
542
|
* single short user-message injection at two specific turn boundaries,
|
|
559
543
|
* and the win on long benchmarks (clean handoff instead of stray
|
|
560
544
|
* exploration on the last turn) is well-documented in Strix's
|
|
561
|
-
* implementation. Disable via
|
|
545
|
+
* implementation. Disable via ZERO_FEATURE_BUDGET_WARNINGS=0 for
|
|
562
546
|
* ablation. Implemented as a getter so the CLI `--features` flag —
|
|
563
547
|
* which sets the env var inside the command action AFTER this module
|
|
564
548
|
* is imported — is still honored at injection time (matches the
|
|
565
549
|
* wpFingerprint / preserveCriticalMessages pattern).
|
|
566
550
|
*/
|
|
567
551
|
get budgetWarnings() {
|
|
568
|
-
return env("
|
|
552
|
+
return env("ZERO_FEATURE_BUDGET_WARNINGS", true);
|
|
569
553
|
},
|
|
570
554
|
/**
|
|
571
555
|
* Per-file orchestration for the research and audit stages (#285).
|
|
@@ -579,14 +563,14 @@ var features = {
|
|
|
579
563
|
* Trade-off: total token spend grows roughly N × per-file budget instead
|
|
580
564
|
* of capped at a single session's budget. For a 50-file package, that
|
|
581
565
|
* could be a 5-10× cost increase on research. Disable via
|
|
582
|
-
* `
|
|
566
|
+
* `ZERO_FEATURE_PER_ITEM_ORCHESTRATION=0` to revert to the shared-session
|
|
583
567
|
* behavior — useful for cost-bounded benchmarks.
|
|
584
568
|
*
|
|
585
569
|
* Implemented as a getter so the env var is honored at orchestration time
|
|
586
570
|
* (matches the wpFingerprint / mongoObjectIdForge pattern).
|
|
587
571
|
*/
|
|
588
572
|
get perItemOrchestration() {
|
|
589
|
-
return env("
|
|
573
|
+
return env("ZERO_FEATURE_PER_ITEM_ORCHESTRATION", true);
|
|
590
574
|
},
|
|
591
575
|
/**
|
|
592
576
|
* JIT skill loading (`packages/core/src/agent/skills/`).
|
|
@@ -595,20 +579,21 @@ var features = {
|
|
|
595
579
|
* them into working context mid-scan. Skills replace the monolithic
|
|
596
580
|
* playbook injection with targeted, on-demand knowledge (#410, #457).
|
|
597
581
|
*
|
|
598
|
-
* Default OFF
|
|
582
|
+
* Default OFF unless an assigned cloud methodology manifest opts this run in.
|
|
583
|
+
* An explicit feature flag still takes precedence over that default.
|
|
599
584
|
* Implemented as a getter so the CLI `--features` flag — which sets
|
|
600
585
|
* the env var inside the command action, AFTER this module has been
|
|
601
586
|
* imported — is still honored at tool-dispatch time.
|
|
602
587
|
*/
|
|
603
588
|
get jitSkills() {
|
|
604
|
-
return env("
|
|
589
|
+
return env("ZERO_FEATURE_JIT_SKILLS", Boolean(process.env["ZERO_AUDIT_SKILLS_MANIFEST"]?.trim()));
|
|
605
590
|
},
|
|
606
591
|
/**
|
|
607
592
|
* Execution-journal shadow mode (#494, first additive slice).
|
|
608
593
|
*
|
|
609
594
|
* When ON, the live agent loop ALSO writes append-only journal entries
|
|
610
595
|
* (`tool_call`, `tool_result`, `finding`, `done`) to
|
|
611
|
-
* `~/.
|
|
596
|
+
* `~/.0/runs/<scanId>/journal.jsonl` as it runs — a durable,
|
|
612
597
|
* replayable trace alongside the existing in-memory conversation window.
|
|
613
598
|
* This is strictly additive: the loop continues to drive off its own
|
|
614
599
|
* conversation state, the journal is write-only here, and a failed
|
|
@@ -621,11 +606,11 @@ var features = {
|
|
|
621
606
|
* moat-ablation harness before any A/B claim. Implemented as a getter so
|
|
622
607
|
* the CLI `--features` flag (which sets the env var inside the command
|
|
623
608
|
* action, AFTER this module has been imported) is honored at loop time.
|
|
624
|
-
* Enable via
|
|
609
|
+
* Enable via ZERO_FEATURE_EXECUTION_JOURNAL=1 or `--features
|
|
625
610
|
* execution-journal`.
|
|
626
611
|
*/
|
|
627
612
|
get executionJournal() {
|
|
628
|
-
return env("
|
|
613
|
+
return env("ZERO_FEATURE_EXECUTION_JOURNAL", false);
|
|
629
614
|
},
|
|
630
615
|
/**
|
|
631
616
|
* Execution-journal context routing (#494, slice 2).
|
|
@@ -641,7 +626,7 @@ var features = {
|
|
|
641
626
|
* Independent of `executionJournal` (the shadow-WRITE flag) on purpose so
|
|
642
627
|
* the moat-ablation harness can toggle write and route separately for a
|
|
643
628
|
* clean A/B. Rehydrate is a READER, though, so it only does anything when
|
|
644
|
-
* a journal was written for the run — it reads `~/.
|
|
629
|
+
* a journal was written for the run — it reads `~/.0/runs/<scanId>/
|
|
645
630
|
* journal.jsonl` regardless of how it got there (shadow mode this slice,
|
|
646
631
|
* or specialists in a later slice). When the journal is missing, empty, or
|
|
647
632
|
* corrupt the loop falls back to the existing DB-blob / fresh-prompt
|
|
@@ -654,11 +639,11 @@ var features = {
|
|
|
654
639
|
* must be explicitly opted into before any A/B claim. Implemented as a
|
|
655
640
|
* getter so the CLI `--features` flag (which sets the env var inside the
|
|
656
641
|
* command action, AFTER this module has been imported) is honored at loop
|
|
657
|
-
* time. Enable via
|
|
642
|
+
* time. Enable via ZERO_FEATURE_JOURNAL_REHYDRATE=1 or `--features
|
|
658
643
|
* journal-rehydrate`.
|
|
659
644
|
*/
|
|
660
645
|
get journalRehydrate() {
|
|
661
|
-
return env("
|
|
646
|
+
return env("ZERO_FEATURE_JOURNAL_REHYDRATE", false);
|
|
662
647
|
},
|
|
663
648
|
/**
|
|
664
649
|
* Loot / foothold ledger for opportunistic exploit chaining (#567).
|
|
@@ -678,14 +663,14 @@ var features = {
|
|
|
678
663
|
* tool), matches the `preserveCriticalMessages` rationale — recovering a
|
|
679
664
|
* credential in turn 12 that's needed in turn 38 is a large win on long-tail
|
|
680
665
|
* challenges — and the cost (a short, size-capped block per turn) is small.
|
|
681
|
-
* Disable via
|
|
666
|
+
* Disable via ZERO_FEATURE_LOOT_LEDGER=0 or `--no-loot-ledger` for
|
|
682
667
|
* ablation. Implemented as a getter so the CLI `--features` flag (which sets
|
|
683
668
|
* the env var inside the command action, AFTER this module has been
|
|
684
669
|
* imported) is honored at tool-dispatch / injection time — matches the
|
|
685
670
|
* wpFingerprint / preserveCriticalMessages pattern.
|
|
686
671
|
*/
|
|
687
672
|
get lootLedger() {
|
|
688
|
-
return env("
|
|
673
|
+
return env("ZERO_FEATURE_LOOT_LEDGER", true);
|
|
689
674
|
},
|
|
690
675
|
/**
|
|
691
676
|
* Typed TODO / plan ledger (`packages/core/src/agent/task-ledger.ts`).
|
|
@@ -704,13 +689,13 @@ var features = {
|
|
|
704
689
|
* failure mode of an unused tool is a few hundred wasted schema tokens
|
|
705
690
|
* rather than wrong behavior. Note for whoever publishes benchmark numbers
|
|
706
691
|
* next: this DOES change the default tool list, so re-baseline before
|
|
707
|
-
* quoting a figure across this change. Disable via
|
|
692
|
+
* quoting a figure across this change. Disable via ZERO_FEATURE_AGENT_PLAN=0
|
|
708
693
|
* or `--features no-agent-plan` for ablation. Getter so the CLI `--features`
|
|
709
694
|
* flag (which sets the env var AFTER this module is imported) is honored at
|
|
710
695
|
* tool-dispatch time.
|
|
711
696
|
*/
|
|
712
697
|
get agentPlan() {
|
|
713
|
-
return env("
|
|
698
|
+
return env("ZERO_FEATURE_AGENT_PLAN", false);
|
|
714
699
|
},
|
|
715
700
|
/**
|
|
716
701
|
* Task-drift detection (`packages/core/src/agent/drift.ts`).
|
|
@@ -733,10 +718,10 @@ var features = {
|
|
|
733
718
|
* to a newly-discovered lead is lexically indistinguishable from a derail.
|
|
734
719
|
* Repo convention is explicit that behavior-steering features stay opt-in
|
|
735
720
|
* until A/B'd, and this is squarely one. Enable via
|
|
736
|
-
*
|
|
721
|
+
* ZERO_FEATURE_DRIFT_DETECTION=1 or `--features drift-detection`.
|
|
737
722
|
*/
|
|
738
723
|
get driftDetection() {
|
|
739
|
-
return env("
|
|
724
|
+
return env("ZERO_FEATURE_DRIFT_DETECTION", false);
|
|
740
725
|
},
|
|
741
726
|
/**
|
|
742
727
|
* OAST out-of-band interaction collaborator + oracle (#659).
|
|
@@ -748,12 +733,12 @@ var features = {
|
|
|
748
733
|
* evidence and feeds the loot ledger.
|
|
749
734
|
*
|
|
750
735
|
* Default OFF — the tools are inert without a deployed collaborator. Enable
|
|
751
|
-
* with
|
|
736
|
+
* with ZERO_FEATURE_OAST=1 AND point ZERO_OAST_URL at the self-hosted
|
|
752
737
|
* collaborator server (see packages/core/src/oast/server.ts). Getter (not a
|
|
753
738
|
* const) so the CLI `--features` flag is honored at tool-dispatch time.
|
|
754
739
|
*/
|
|
755
740
|
get oastCollaborator() {
|
|
756
|
-
return env("
|
|
741
|
+
return env("ZERO_FEATURE_OAST", false);
|
|
757
742
|
},
|
|
758
743
|
/**
|
|
759
744
|
* Anthropic prompt caching (`cache_control: {type: "ephemeral"}`) over the
|
|
@@ -776,17 +761,17 @@ var features = {
|
|
|
776
761
|
* never see an Anthropic-shaped field regardless of this flag (see
|
|
777
762
|
* `providerSupportsPromptCache`).
|
|
778
763
|
*
|
|
779
|
-
* Disable via
|
|
764
|
+
* Disable via ZERO_FEATURE_PROMPT_CACHE=0 — worth doing only to isolate a
|
|
780
765
|
* suspected provider-side caching bug, or to measure the uncached baseline.
|
|
781
766
|
* Implemented as a getter so a late env mutation (CLI `--features`, which
|
|
782
767
|
* runs after this module is imported) is honoured at request-build time.
|
|
783
768
|
*/
|
|
784
769
|
get promptCache() {
|
|
785
|
-
return env("
|
|
770
|
+
return env("ZERO_FEATURE_PROMPT_CACHE", true);
|
|
786
771
|
}
|
|
787
772
|
};
|
|
788
773
|
function presetRaisesDefault(key) {
|
|
789
|
-
const raw = process.env["
|
|
774
|
+
const raw = process.env["ZERO_FEATURE_PRESET"];
|
|
790
775
|
if (!raw)
|
|
791
776
|
return false;
|
|
792
777
|
const preset = resolveFeaturePreset(raw);
|
|
@@ -884,7 +869,7 @@ function sanitizeFields(input) {
|
|
|
884
869
|
var LEVEL_RANK = { info: 10, warn: 20, error: 30 };
|
|
885
870
|
var OFF_RANK = Number.POSITIVE_INFINITY;
|
|
886
871
|
function minimumRank() {
|
|
887
|
-
const raw = process.env["
|
|
872
|
+
const raw = process.env["ZERO_DIAG_LEVEL"];
|
|
888
873
|
if (!raw)
|
|
889
874
|
return LEVEL_RANK.info;
|
|
890
875
|
switch (raw.trim().toLowerCase()) {
|
|
@@ -904,9 +889,9 @@ function minimumRank() {
|
|
|
904
889
|
function formatDiagnosticLine(event) {
|
|
905
890
|
const keys = Object.keys(event.fields);
|
|
906
891
|
if (keys.length === 0)
|
|
907
|
-
return `[
|
|
892
|
+
return `[0] ${event.message}`;
|
|
908
893
|
const rendered = keys.map((k) => `${k}=${event.fields[k]}`).join(" ");
|
|
909
|
-
return `[
|
|
894
|
+
return `[0] ${event.message} (${rendered})`;
|
|
910
895
|
}
|
|
911
896
|
var stderrDiagnosticSink = {
|
|
912
897
|
emit(event) {
|
|
@@ -1087,19 +1072,22 @@ function loadCloudCredentials(opts = {}) {
|
|
|
1087
1072
|
const env2 = opts.env ?? process.env;
|
|
1088
1073
|
const warn = opts.warn ?? ((m) => process.stderr.write(`${m}
|
|
1089
1074
|
`));
|
|
1090
|
-
const envTok = env2["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1075
|
+
const envTok = env2["ZERO_CLOUD_TOKEN"]?.trim() || env2["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1091
1076
|
if (envTok) {
|
|
1092
|
-
const envHost = normaliseHost(env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1077
|
+
const envHost = normaliseHost(env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1078
|
+
if (!env2["ZERO_CLOUD_TOKEN"]?.trim()) {
|
|
1079
|
+
warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
|
|
1080
|
+
}
|
|
1093
1081
|
return { host: envHost, token: envTok, source: "env" };
|
|
1094
1082
|
}
|
|
1095
|
-
const path = join(
|
|
1083
|
+
const path = join(cloudStateDir(opts.homeDir, env2), "cloud.env");
|
|
1096
1084
|
let raw;
|
|
1097
1085
|
try {
|
|
1098
1086
|
raw = readFileSync(path, "utf-8");
|
|
1099
1087
|
} catch (err) {
|
|
1100
1088
|
const code = err.code;
|
|
1101
1089
|
if (code === "ENOENT") {
|
|
1102
|
-
throw new CloudAuthMissingError(`
|
|
1090
|
+
throw new CloudAuthMissingError(`0-cloud credentials not found. Run \`${env2["ZERO_DEV_SOURCE_ROOT"]?.trim() ? "0dev" : "0"} auth login\`.`);
|
|
1103
1091
|
}
|
|
1104
1092
|
throw err;
|
|
1105
1093
|
}
|
|
@@ -1107,22 +1095,25 @@ function loadCloudCredentials(opts = {}) {
|
|
|
1107
1095
|
const st = statSync(path);
|
|
1108
1096
|
const mode = st.mode & 511;
|
|
1109
1097
|
if (mode !== 384) {
|
|
1110
|
-
warn(`[
|
|
1098
|
+
warn(`[0 cloud] WARNING: ${path} mode is ${mode.toString(8).padStart(3, "0")} (expected 600). Run: chmod 600 ${path}`);
|
|
1111
1099
|
}
|
|
1112
1100
|
} catch {
|
|
1113
1101
|
}
|
|
1114
1102
|
const parsed = parseEnvFile(raw);
|
|
1115
|
-
const fileTok = parsed["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1103
|
+
const fileTok = parsed["ZERO_CLOUD_TOKEN"]?.trim() || parsed["0SEC_CLOUD_TOKEN"]?.trim();
|
|
1116
1104
|
if (!fileTok) {
|
|
1117
|
-
throw new CloudAuthMissingError(`
|
|
1105
|
+
throw new CloudAuthMissingError(`0-cloud credentials in ${path} are incomplete: ZERO_CLOUD_TOKEN is required.`);
|
|
1106
|
+
}
|
|
1107
|
+
if (!parsed["ZERO_CLOUD_TOKEN"]?.trim()) {
|
|
1108
|
+
warn("[0 cloud] using legacy 0SEC_CLOUD_* credentials; re-run `0 auth login` to migrate to ZERO_CLOUD_*.");
|
|
1118
1109
|
}
|
|
1119
|
-
const fileHost = normaliseHost(parsed["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1110
|
+
const fileHost = normaliseHost(parsed["ZERO_CLOUD_HOST"]?.trim() ?? parsed["0SEC_CLOUD_HOST"]?.trim() ?? env2["ZERO_CLOUD_HOST"]?.trim() ?? env2["0SEC_CLOUD_HOST"]?.trim() ?? DEFAULT_CLOUD_HOST);
|
|
1120
1111
|
return { host: fileHost, token: fileTok, source: "file" };
|
|
1121
1112
|
}
|
|
1122
1113
|
function normaliseHost(host) {
|
|
1123
1114
|
let h = host;
|
|
1124
1115
|
if (!/^https?:\/\//.test(h)) {
|
|
1125
|
-
throw new CloudAuthMissingError(`
|
|
1116
|
+
throw new CloudAuthMissingError(`ZERO_CLOUD_HOST must be an http(s) URL (got ${JSON.stringify(host)}).`);
|
|
1126
1117
|
}
|
|
1127
1118
|
while (h.endsWith("/"))
|
|
1128
1119
|
h = h.slice(0, -1);
|
|
@@ -1167,26 +1158,92 @@ var CloudError = class extends Error {
|
|
|
1167
1158
|
};
|
|
1168
1159
|
var CloudUnauthorizedError = class extends CloudError {
|
|
1169
1160
|
constructor(path) {
|
|
1170
|
-
super(`
|
|
1161
|
+
super(`0-cloud auth rejected (HTTP 401) on ${path}. Run \`0 auth login\` to refresh.`, 401, path);
|
|
1171
1162
|
this.name = "CloudUnauthorizedError";
|
|
1172
1163
|
}
|
|
1173
1164
|
};
|
|
1174
1165
|
var CloudForbiddenError = class extends CloudError {
|
|
1175
1166
|
constructor(path) {
|
|
1176
|
-
super(`
|
|
1167
|
+
super(`0-cloud forbidden (HTTP 403) on ${path}. Token lacks scope for this resource.`, 403, path);
|
|
1177
1168
|
this.name = "CloudForbiddenError";
|
|
1178
1169
|
}
|
|
1179
1170
|
};
|
|
1180
1171
|
var CloudNetworkError = class extends CloudError {
|
|
1181
1172
|
constructor(message, path) {
|
|
1182
|
-
super(`
|
|
1173
|
+
super(`0-cloud network error on ${path}: ${message}`, void 0, path);
|
|
1183
1174
|
this.name = "CloudNetworkError";
|
|
1184
1175
|
}
|
|
1185
1176
|
};
|
|
1177
|
+
function isUsageAccount(raw) {
|
|
1178
|
+
if (!raw || typeof raw !== "object")
|
|
1179
|
+
return false;
|
|
1180
|
+
const obj = raw;
|
|
1181
|
+
if (obj.schemaVersion !== "usage-v2")
|
|
1182
|
+
return false;
|
|
1183
|
+
if (!isUtcDate(obj.snapshotAt))
|
|
1184
|
+
return false;
|
|
1185
|
+
if (!obj.scope || typeof obj.scope !== "object")
|
|
1186
|
+
return false;
|
|
1187
|
+
if (typeof obj.scope.orgId !== "string")
|
|
1188
|
+
return false;
|
|
1189
|
+
if (typeof obj.state !== "string")
|
|
1190
|
+
return false;
|
|
1191
|
+
if (!["ready", "disabled", "unavailable", "restricted"].includes(obj.state))
|
|
1192
|
+
return false;
|
|
1193
|
+
if (obj.reason !== null && typeof obj.reason !== "string")
|
|
1194
|
+
return false;
|
|
1195
|
+
const plan = obj.plan;
|
|
1196
|
+
if (!plan || typeof plan !== "object")
|
|
1197
|
+
return false;
|
|
1198
|
+
if (typeof plan.id !== "string" && plan.id !== null)
|
|
1199
|
+
return false;
|
|
1200
|
+
if (plan.id !== null && !["pro", "gold", "enterprise"].includes(plan.id))
|
|
1201
|
+
return false;
|
|
1202
|
+
if (typeof plan.name !== "string" && plan.name !== null)
|
|
1203
|
+
return false;
|
|
1204
|
+
if (!isUsd(plan.monthlyPriceUsd))
|
|
1205
|
+
return false;
|
|
1206
|
+
const included = obj.included;
|
|
1207
|
+
if (!included || typeof included !== "object")
|
|
1208
|
+
return false;
|
|
1209
|
+
if (typeof included.state !== "string")
|
|
1210
|
+
return false;
|
|
1211
|
+
if (!["active", "exhausted", "none", "unavailable"].includes(included.state))
|
|
1212
|
+
return false;
|
|
1213
|
+
if (included.usedPercent !== null && typeof included.usedPercent !== "number")
|
|
1214
|
+
return false;
|
|
1215
|
+
if (included.usedPercent !== null && (!Number.isFinite(included.usedPercent) || included.usedPercent < 0 || included.usedPercent > 100))
|
|
1216
|
+
return false;
|
|
1217
|
+
if (included.resetsAt !== null && !isUtcDate(included.resetsAt))
|
|
1218
|
+
return false;
|
|
1219
|
+
const prepaid = obj.prepaid;
|
|
1220
|
+
if (!prepaid || typeof prepaid !== "object")
|
|
1221
|
+
return false;
|
|
1222
|
+
if (!isUsd(prepaid.balanceUsd))
|
|
1223
|
+
return false;
|
|
1224
|
+
if (typeof prepaid.fallbackEnabled !== "boolean")
|
|
1225
|
+
return false;
|
|
1226
|
+
if (typeof obj.canManageBilling !== "boolean")
|
|
1227
|
+
return false;
|
|
1228
|
+
const admission = obj.admission;
|
|
1229
|
+
if (!admission || typeof admission !== "object")
|
|
1230
|
+
return false;
|
|
1231
|
+
if (typeof admission.eligible !== "boolean")
|
|
1232
|
+
return false;
|
|
1233
|
+
if (admission.reason !== null && typeof admission.reason !== "string")
|
|
1234
|
+
return false;
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
function isUsd(value) {
|
|
1238
|
+
return value === null || typeof value === "string" && /^\d+(?:\.\d{1,9})?$/.test(value);
|
|
1239
|
+
}
|
|
1240
|
+
function isUtcDate(value) {
|
|
1241
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/.test(value) && Number.isFinite(Date.parse(value));
|
|
1242
|
+
}
|
|
1186
1243
|
function healthPath(host) {
|
|
1187
1244
|
try {
|
|
1188
1245
|
const hostname = new URL(host).hostname.toLowerCase();
|
|
1189
|
-
if (hostname === "cloud.
|
|
1246
|
+
if (hostname === "cloud.0.ai" || hostname === "cloud.0.security") {
|
|
1190
1247
|
return "/api/health";
|
|
1191
1248
|
}
|
|
1192
1249
|
} catch {
|
|
@@ -1219,25 +1276,46 @@ var CloudClient = class {
|
|
|
1219
1276
|
return this.getJson("/api/inference/v1/models");
|
|
1220
1277
|
}
|
|
1221
1278
|
/**
|
|
1222
|
-
* Fetch the organization's
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1279
|
+
* Fetch the organization's credit account — usage-v2 shape including
|
|
1280
|
+
* plan, included allowance, prepaid balance, and admission status.
|
|
1281
|
+
*
|
|
1282
|
+
* Returns `null` when the response is a recognised HTTP 200 (customer is
|
|
1283
|
+
* authenticated) but the payload is missing, legacy, or structurally
|
|
1284
|
+
* unrecognised — not an auth failure. HTTP 401/403 still throw the
|
|
1285
|
+
* existing typed errors so the caller can distinguish a credential
|
|
1286
|
+
* problem from unsupported credit data.
|
|
1287
|
+
*
|
|
1288
|
+
* Monetary amounts are decimal strings (no Number coercion); the
|
|
1289
|
+
* caller preserves them for exact display.
|
|
1225
1290
|
*/
|
|
1226
1291
|
async getInferenceAccount() {
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1292
|
+
const raw = await this.getJson("/api/inference/account");
|
|
1293
|
+
if (!isUsageAccount(raw))
|
|
1294
|
+
return null;
|
|
1295
|
+
const { plan, included, prepaid, admission } = raw;
|
|
1296
|
+
return {
|
|
1297
|
+
schemaVersion: raw.schemaVersion,
|
|
1298
|
+
snapshotAt: raw.snapshotAt,
|
|
1299
|
+
scope: { orgId: raw.scope.orgId },
|
|
1300
|
+
state: raw.state,
|
|
1301
|
+
reason: raw.reason,
|
|
1302
|
+
plan: {
|
|
1303
|
+
id: plan.id,
|
|
1304
|
+
name: plan.name,
|
|
1305
|
+
monthlyPriceUsd: plan.monthlyPriceUsd
|
|
1306
|
+
},
|
|
1307
|
+
included: {
|
|
1308
|
+
state: included.state,
|
|
1309
|
+
usedPercent: included.usedPercent,
|
|
1310
|
+
resetsAt: included.resetsAt
|
|
1311
|
+
},
|
|
1312
|
+
prepaid: {
|
|
1313
|
+
balanceUsd: prepaid.balanceUsd,
|
|
1314
|
+
fallbackEnabled: prepaid.fallbackEnabled
|
|
1315
|
+
},
|
|
1316
|
+
canManageBilling: raw.canManageBilling,
|
|
1317
|
+
admission: { eligible: admission.eligible, reason: admission.reason }
|
|
1318
|
+
};
|
|
1241
1319
|
}
|
|
1242
1320
|
/**
|
|
1243
1321
|
* Fetch request-level usage metadata for the operator's hosted
|
|
@@ -1247,6 +1325,114 @@ var CloudClient = class {
|
|
|
1247
1325
|
async getInferenceUsage() {
|
|
1248
1326
|
return this.getJson("/api/inference/usage");
|
|
1249
1327
|
}
|
|
1328
|
+
// ── Audit-skills helpers (#audit-skills) ──
|
|
1329
|
+
/** List all audit skills for the authenticated organization. */
|
|
1330
|
+
async listAuditSkills() {
|
|
1331
|
+
return this.getJson("/api/audit-skills");
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Get a single audit skill with its revision history and project
|
|
1335
|
+
* assignments.
|
|
1336
|
+
*/
|
|
1337
|
+
async getAuditSkill(id) {
|
|
1338
|
+
return this.getJson(`/api/audit-skills/${encodeURIComponent(id)}`);
|
|
1339
|
+
}
|
|
1340
|
+
/** Create a new markdown-based audit skill. */
|
|
1341
|
+
async createAuditSkill(input) {
|
|
1342
|
+
return this.postJson("/api/audit-skills", input);
|
|
1343
|
+
}
|
|
1344
|
+
/** Import an audit skill from a GitHub repository. */
|
|
1345
|
+
async importAuditSkillFromGithub(input) {
|
|
1346
|
+
return this.postJson("/api/audit-skills/import", input);
|
|
1347
|
+
}
|
|
1348
|
+
/** Create a new revision of an audit skill (CAS — 409 on stale expectedRevision). */
|
|
1349
|
+
async createAuditSkillRevision(id, input) {
|
|
1350
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/revisions`, input);
|
|
1351
|
+
}
|
|
1352
|
+
/** Re-fetch the skill from its original GitHub source. */
|
|
1353
|
+
async syncAuditSkill(id, expectedRevision) {
|
|
1354
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/sync`, { expectedRevision });
|
|
1355
|
+
}
|
|
1356
|
+
/** Pin an audit skill revision to a project for future scans. */
|
|
1357
|
+
async assignAuditSkill(id, projectId, revisionId) {
|
|
1358
|
+
return this.postJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`, { revisionId });
|
|
1359
|
+
}
|
|
1360
|
+
/** Unpin an audit skill from a project (future scans no longer use it). */
|
|
1361
|
+
async unassignAuditSkill(id, projectId) {
|
|
1362
|
+
return this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}/projects/${encodeURIComponent(projectId)}`);
|
|
1363
|
+
}
|
|
1364
|
+
/** Archive an audit skill (disables future bindings, preserves history). */
|
|
1365
|
+
async archiveAuditSkill(id) {
|
|
1366
|
+
await this.deleteJson(`/api/audit-skills/${encodeURIComponent(id)}`);
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* List audit skills available to a specific project, along with current
|
|
1370
|
+
* assignments for that project. The project must belong to the caller's org.
|
|
1371
|
+
*/
|
|
1372
|
+
async listAuditSkillsByProject(projectId) {
|
|
1373
|
+
return this.getJson(`/api/audit-skills/by-project/${encodeURIComponent(projectId)}`);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Generic JSON DELETE helper with the same error mapping as getJson/postJson.
|
|
1377
|
+
* Used by `0 service disconnect` to remove scan schedules.
|
|
1378
|
+
*/
|
|
1379
|
+
async deleteJson(path) {
|
|
1380
|
+
const url = `${this.host}${path}`;
|
|
1381
|
+
let res;
|
|
1382
|
+
try {
|
|
1383
|
+
res = await this.fetchImpl(url, {
|
|
1384
|
+
method: "DELETE",
|
|
1385
|
+
headers: { ...this.headers(), "Content-Type": "application/json" }
|
|
1386
|
+
});
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1389
|
+
throw new CloudNetworkError(this.scrub(msg), path);
|
|
1390
|
+
}
|
|
1391
|
+
if (!res.ok) {
|
|
1392
|
+
let code;
|
|
1393
|
+
try {
|
|
1394
|
+
const parsed = await res.json();
|
|
1395
|
+
const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
|
|
1396
|
+
if (typeof raw === "string" && raw.length > 0)
|
|
1397
|
+
code = raw;
|
|
1398
|
+
} catch {
|
|
1399
|
+
}
|
|
1400
|
+
this.throwForStatus(res.status, path, code);
|
|
1401
|
+
}
|
|
1402
|
+
if (res.status === 204)
|
|
1403
|
+
return void 0;
|
|
1404
|
+
return await res.json();
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Generic JSON POST helper with the same error mapping as getJson.
|
|
1408
|
+
* Used by `0 connect` to enqueue scans and schedules.
|
|
1409
|
+
*/
|
|
1410
|
+
async postJson(path, body) {
|
|
1411
|
+
const url = `${this.host}${path}`;
|
|
1412
|
+
let res;
|
|
1413
|
+
try {
|
|
1414
|
+
res = await this.fetchImpl(url, {
|
|
1415
|
+
method: "POST",
|
|
1416
|
+
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
1417
|
+
body: JSON.stringify(body)
|
|
1418
|
+
});
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1421
|
+
throw new CloudNetworkError(this.scrub(msg), path);
|
|
1422
|
+
}
|
|
1423
|
+
if (!res.ok) {
|
|
1424
|
+
let code;
|
|
1425
|
+
try {
|
|
1426
|
+
const parsed = await res.json();
|
|
1427
|
+
const raw = typeof parsed?.error === "object" ? parsed.error?.code : void 0;
|
|
1428
|
+
if (typeof raw === "string" && raw.length > 0)
|
|
1429
|
+
code = raw;
|
|
1430
|
+
} catch {
|
|
1431
|
+
}
|
|
1432
|
+
this.throwForStatus(res.status, path, code);
|
|
1433
|
+
}
|
|
1434
|
+
return await res.json();
|
|
1435
|
+
}
|
|
1250
1436
|
/**
|
|
1251
1437
|
* Generic JSON GET helper. Public so future modules (scans, findings)
|
|
1252
1438
|
* can reuse the same error mapping without duplicating it. Not exported
|
|
@@ -1283,7 +1469,7 @@ var CloudClient = class {
|
|
|
1283
1469
|
throw new CloudUnauthorizedError(path);
|
|
1284
1470
|
if (status === 403)
|
|
1285
1471
|
throw new CloudForbiddenError(path);
|
|
1286
|
-
throw new CloudError(`
|
|
1472
|
+
throw new CloudError(`0-cloud request failed (HTTP ${status}${code ? ` ${code}` : ""}) on ${path}.`, status, path, code);
|
|
1287
1473
|
}
|
|
1288
1474
|
/**
|
|
1289
1475
|
* Throw a typed error for non-2xx responses. Public so direct callers
|
|
@@ -1296,14 +1482,14 @@ var CloudClient = class {
|
|
|
1296
1482
|
throw new CloudUnauthorizedError(path);
|
|
1297
1483
|
if (res.status === 403)
|
|
1298
1484
|
throw new CloudForbiddenError(path);
|
|
1299
|
-
throw new CloudError(`
|
|
1485
|
+
throw new CloudError(`0-cloud request failed (HTTP ${res.status}) on ${path}.`, res.status, path);
|
|
1300
1486
|
}
|
|
1301
1487
|
// ── internals ──
|
|
1302
1488
|
headers() {
|
|
1303
1489
|
return {
|
|
1304
1490
|
Authorization: `Bearer ${this.token}`,
|
|
1305
1491
|
Accept: "application/json",
|
|
1306
|
-
"User-Agent":
|
|
1492
|
+
"User-Agent": `@0/cli/${VERSION}`
|
|
1307
1493
|
};
|
|
1308
1494
|
}
|
|
1309
1495
|
/**
|
|
@@ -1325,6 +1511,54 @@ import { appendFileSync, existsSync, readFileSync as readFileSync2, renameSync,
|
|
|
1325
1511
|
import { homedir } from "node:os";
|
|
1326
1512
|
import { join as join2 } from "node:path";
|
|
1327
1513
|
|
|
1514
|
+
// packages/core/dist/runtime/hosted-request-queue.js
|
|
1515
|
+
var MAX_IN_FLIGHT = 4;
|
|
1516
|
+
var endpoints = /* @__PURE__ */ new Map();
|
|
1517
|
+
function acquireHostedRequestSlot(endpoint, token, signal) {
|
|
1518
|
+
signal?.throwIfAborted();
|
|
1519
|
+
let accounts = endpoints.get(endpoint);
|
|
1520
|
+
if (!accounts)
|
|
1521
|
+
endpoints.set(endpoint, accounts = /* @__PURE__ */ new Map());
|
|
1522
|
+
let queue = accounts.get(token);
|
|
1523
|
+
if (!queue)
|
|
1524
|
+
accounts.set(token, queue = { active: 0, waiting: [] });
|
|
1525
|
+
const accountQueues = accounts;
|
|
1526
|
+
const current = queue;
|
|
1527
|
+
return new Promise((resolve, reject) => {
|
|
1528
|
+
const abort = () => {
|
|
1529
|
+
const index = current.waiting.indexOf(waiter);
|
|
1530
|
+
if (index !== -1)
|
|
1531
|
+
current.waiting.splice(index, 1);
|
|
1532
|
+
reject(signal.reason);
|
|
1533
|
+
};
|
|
1534
|
+
const waiter = { grant: () => {
|
|
1535
|
+
signal?.removeEventListener("abort", abort);
|
|
1536
|
+
current.active++;
|
|
1537
|
+
let released = false;
|
|
1538
|
+
resolve(() => {
|
|
1539
|
+
if (released)
|
|
1540
|
+
return;
|
|
1541
|
+
released = true;
|
|
1542
|
+
current.active--;
|
|
1543
|
+
const next = current.waiting.shift();
|
|
1544
|
+
if (next)
|
|
1545
|
+
next.grant();
|
|
1546
|
+
else if (current.active === 0) {
|
|
1547
|
+
accountQueues.delete(token);
|
|
1548
|
+
if (accountQueues.size === 0)
|
|
1549
|
+
endpoints.delete(endpoint);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
} };
|
|
1553
|
+
if (current.active < MAX_IN_FLIGHT)
|
|
1554
|
+
waiter.grant();
|
|
1555
|
+
else {
|
|
1556
|
+
current.waiting.push(waiter);
|
|
1557
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1328
1562
|
// packages/core/dist/runtime/prompt-cache.js
|
|
1329
1563
|
var MAX_CACHE_BREAKPOINTS = 4;
|
|
1330
1564
|
var MESSAGE_CACHE_BREAKPOINTS = MAX_CACHE_BREAKPOINTS - 1;
|
|
@@ -1339,7 +1573,7 @@ function providerSupportsPromptCache(provider) {
|
|
|
1339
1573
|
return readExtraCacheProviders().has(provider);
|
|
1340
1574
|
}
|
|
1341
1575
|
function readExtraCacheProviders() {
|
|
1342
|
-
const raw = process.env["
|
|
1576
|
+
const raw = process.env["ZERO_PROMPT_CACHE_EXTRA_PROVIDERS"];
|
|
1343
1577
|
if (!raw)
|
|
1344
1578
|
return /* @__PURE__ */ new Set();
|
|
1345
1579
|
return new Set(raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0));
|
|
@@ -1408,7 +1642,7 @@ function isWireBlockArray(blocks) {
|
|
|
1408
1642
|
}
|
|
1409
1643
|
var azureRegionCache = /* @__PURE__ */ new Map();
|
|
1410
1644
|
async function probeAzureRegion(baseUrl, apiKey, fetchImpl = fetch) {
|
|
1411
|
-
const override = process.env["
|
|
1645
|
+
const override = process.env["ZERO_REGION_OVERRIDE"];
|
|
1412
1646
|
if (override && override.trim().length > 0) {
|
|
1413
1647
|
return override.trim();
|
|
1414
1648
|
}
|
|
@@ -1470,7 +1704,7 @@ function prettyRegion(code) {
|
|
|
1470
1704
|
};
|
|
1471
1705
|
return map[code.toLowerCase()] ?? code;
|
|
1472
1706
|
}
|
|
1473
|
-
var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("
|
|
1707
|
+
var PROVIDER_BANNER_KEY = /* @__PURE__ */ Symbol.for("0.core.loggedProviderStartup");
|
|
1474
1708
|
var loggedProviderStartup = (() => {
|
|
1475
1709
|
const g = globalThis;
|
|
1476
1710
|
if (!g[PROVIDER_BANNER_KEY])
|
|
@@ -1478,7 +1712,7 @@ var loggedProviderStartup = (() => {
|
|
|
1478
1712
|
return g[PROVIDER_BANNER_KEY];
|
|
1479
1713
|
})();
|
|
1480
1714
|
function appendNativeTrace(record) {
|
|
1481
|
-
const file = process.env["
|
|
1715
|
+
const file = process.env["ZERO_TRACE_NATIVE_RESPONSES"];
|
|
1482
1716
|
if (!file)
|
|
1483
1717
|
return;
|
|
1484
1718
|
try {
|
|
@@ -1488,7 +1722,7 @@ function appendNativeTrace(record) {
|
|
|
1488
1722
|
}
|
|
1489
1723
|
}
|
|
1490
1724
|
function shouldLogProviderStartup() {
|
|
1491
|
-
return process.env["
|
|
1725
|
+
return process.env["ZERO_SUPPRESS_PROVIDER_STARTUP_LOG"] !== "1";
|
|
1492
1726
|
}
|
|
1493
1727
|
function isRetryableHttpStatus(status) {
|
|
1494
1728
|
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
@@ -1498,7 +1732,7 @@ var TRANSIENT_STREAM_ERROR_PATTERNS = [
|
|
|
1498
1732
|
"response stream failed"
|
|
1499
1733
|
];
|
|
1500
1734
|
function llmStreamMaxAttempts() {
|
|
1501
|
-
const raw = process.env["
|
|
1735
|
+
const raw = process.env["ZERO_LLM_STREAM_MAX_ATTEMPTS"];
|
|
1502
1736
|
if (raw == null || raw.trim() === "")
|
|
1503
1737
|
return 3;
|
|
1504
1738
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1545,28 +1779,28 @@ function isRetryableTransportCode(code) {
|
|
|
1545
1779
|
].includes(code);
|
|
1546
1780
|
}
|
|
1547
1781
|
function llmMaxRetries() {
|
|
1548
|
-
const raw = process.env["
|
|
1782
|
+
const raw = process.env["ZERO_LLM_MAX_RETRIES"];
|
|
1549
1783
|
if (raw == null || raw.trim() === "")
|
|
1550
1784
|
return 6;
|
|
1551
1785
|
const n = Number.parseInt(raw, 10);
|
|
1552
1786
|
return Number.isFinite(n) && n >= 0 ? n : 6;
|
|
1553
1787
|
}
|
|
1554
1788
|
function llmMaxRetryWaitMs() {
|
|
1555
|
-
const raw = process.env["
|
|
1789
|
+
const raw = process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
|
|
1556
1790
|
if (raw == null || raw.trim() === "")
|
|
1557
1791
|
return 6e4;
|
|
1558
1792
|
const n = Number.parseInt(raw, 10);
|
|
1559
1793
|
return Number.isFinite(n) && n > 0 ? n : 6e4;
|
|
1560
1794
|
}
|
|
1561
1795
|
function llm429MaxRetries() {
|
|
1562
|
-
const raw = process.env["
|
|
1796
|
+
const raw = process.env["ZERO_LLM_429_MAX_RETRIES"] ?? process.env["ZERO_LLM_MAX_RETRIES"];
|
|
1563
1797
|
if (raw == null || raw.trim() === "")
|
|
1564
1798
|
return 12;
|
|
1565
1799
|
const n = Number.parseInt(raw, 10);
|
|
1566
1800
|
return Number.isFinite(n) && n >= 0 ? n : 12;
|
|
1567
1801
|
}
|
|
1568
1802
|
function llm429MaxRetryWaitMs() {
|
|
1569
|
-
const raw = process.env["
|
|
1803
|
+
const raw = process.env["ZERO_LLM_429_MAX_RETRY_WAIT_MS"] ?? process.env["ZERO_LLM_MAX_RETRY_WAIT_MS"];
|
|
1570
1804
|
if (raw == null || raw.trim() === "")
|
|
1571
1805
|
return 3e5;
|
|
1572
1806
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1677,12 +1911,19 @@ function parseUsageLimitReached(body) {
|
|
|
1677
1911
|
return details;
|
|
1678
1912
|
}
|
|
1679
1913
|
function llmStreamIdleTimeoutMs() {
|
|
1680
|
-
const raw = process.env["
|
|
1914
|
+
const raw = process.env["ZERO_LLM_STREAM_IDLE_TIMEOUT_MS"];
|
|
1681
1915
|
if (raw == null || raw.trim() === "")
|
|
1682
1916
|
return 12e4;
|
|
1683
1917
|
const n = Number.parseInt(raw, 10);
|
|
1684
1918
|
return Number.isFinite(n) && n > 0 ? n : 12e4;
|
|
1685
1919
|
}
|
|
1920
|
+
function llmStreamEventIdleTimeoutMs() {
|
|
1921
|
+
const raw = process.env["ZERO_LLM_STREAM_EVENT_IDLE_TIMEOUT_MS"];
|
|
1922
|
+
if (raw == null || raw.trim() === "")
|
|
1923
|
+
return 24e4;
|
|
1924
|
+
const n = Number.parseInt(raw, 10);
|
|
1925
|
+
return Number.isFinite(n) && n > 0 ? n : 24e4;
|
|
1926
|
+
}
|
|
1686
1927
|
function parseRetryAfterMs(headerValue) {
|
|
1687
1928
|
if (!headerValue)
|
|
1688
1929
|
return void 0;
|
|
@@ -1827,7 +2068,7 @@ var AZURE_FOUNDRY_DEPLOYMENT_IDS = {
|
|
|
1827
2068
|
"gpt-5.6-terra": true
|
|
1828
2069
|
};
|
|
1829
2070
|
function parseLlmFallbackChain(env2 = process.env) {
|
|
1830
|
-
const raw = env2["
|
|
2071
|
+
const raw = env2["ZERO_LLM_FALLBACK"];
|
|
1831
2072
|
if (!raw || raw.trim().length === 0)
|
|
1832
2073
|
return [];
|
|
1833
2074
|
const entries = [];
|
|
@@ -1853,17 +2094,17 @@ function parseLlmFallbackChain(env2 = process.env) {
|
|
|
1853
2094
|
continue;
|
|
1854
2095
|
const colonIdx = trimmed.indexOf(":");
|
|
1855
2096
|
if (colonIdx < 1 || colonIdx === trimmed.length - 1) {
|
|
1856
|
-
diag.warn("fallback_chain_malformed_entry", `
|
|
2097
|
+
diag.warn("fallback_chain_malformed_entry", `ZERO_LLM_FALLBACK: malformed entry "${trimmed}" (expected provider:model)`, { entry: trimmed, expected: "provider:model" });
|
|
1857
2098
|
continue;
|
|
1858
2099
|
}
|
|
1859
2100
|
const provider = trimmed.slice(0, colonIdx);
|
|
1860
2101
|
const model = trimmed.slice(colonIdx + 1).trim();
|
|
1861
2102
|
if (!VALID_PROVIDERS[provider]) {
|
|
1862
|
-
diag.warn("fallback_chain_unknown_provider", `
|
|
2103
|
+
diag.warn("fallback_chain_unknown_provider", `ZERO_LLM_FALLBACK: unknown provider "${provider}" in "${trimmed}"`, { entry: trimmed, provider });
|
|
1863
2104
|
continue;
|
|
1864
2105
|
}
|
|
1865
2106
|
if (!model) {
|
|
1866
|
-
diag.warn("fallback_chain_empty_model", `
|
|
2107
|
+
diag.warn("fallback_chain_empty_model", `ZERO_LLM_FALLBACK: empty model in "${trimmed}"`, { entry: trimmed, provider });
|
|
1867
2108
|
continue;
|
|
1868
2109
|
}
|
|
1869
2110
|
entries.push({ provider, model });
|
|
@@ -1906,7 +2147,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
1906
2147
|
return { apiKey: key, baseUrl: env2.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com", wireApi: "chat_completions" };
|
|
1907
2148
|
}
|
|
1908
2149
|
case "chatgpt-codex": {
|
|
1909
|
-
if (!env2["
|
|
2150
|
+
if (!env2["ZERO_CHATGPT_ACCESS_TOKEN"] && !env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"] && !readChatGptCodexAuthFile(env2))
|
|
1910
2151
|
return void 0;
|
|
1911
2152
|
return { apiKey: "", baseUrl: CODEX_API_ENDPOINT, wireApi: "responses" };
|
|
1912
2153
|
}
|
|
@@ -1941,13 +2182,13 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
1941
2182
|
return { apiKey: key, baseUrl: env2.OPENCODE_BASE_URL ?? OPENCODE_DEFAULT_BASE_URL, wireApi: opencodeWireApiForModel(model) };
|
|
1942
2183
|
}
|
|
1943
2184
|
case "copilot": {
|
|
1944
|
-
const key = apiKey ?? env2["
|
|
2185
|
+
const key = apiKey ?? env2["ZERO_COPILOT_GITHUB_TOKEN"];
|
|
1945
2186
|
if (!key)
|
|
1946
2187
|
return void 0;
|
|
1947
2188
|
return { apiKey: key, baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE, wireApi: "chat_completions" };
|
|
1948
2189
|
}
|
|
1949
2190
|
case "google": {
|
|
1950
|
-
if (!env2["
|
|
2191
|
+
if (!env2["ZERO_GEMINI_ACCESS_TOKEN"] && !env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"])
|
|
1951
2192
|
return void 0;
|
|
1952
2193
|
return { apiKey: "", baseUrl: CODE_ASSIST_ENDPOINT, wireApi: "google_generate_content" };
|
|
1953
2194
|
}
|
|
@@ -1973,7 +2214,7 @@ function resolveFailoverProvider(provider, model, env2 = process.env, apiKey) {
|
|
|
1973
2214
|
}
|
|
1974
2215
|
var fallbackChainCache;
|
|
1975
2216
|
function getFallbackChain(env2) {
|
|
1976
|
-
const raw = env2["
|
|
2217
|
+
const raw = env2["ZERO_LLM_FALLBACK"];
|
|
1977
2218
|
if (!fallbackChainCache || fallbackChainCache.raw !== raw) {
|
|
1978
2219
|
fallbackChainCache = { raw, entries: parseLlmFallbackChain(env2) };
|
|
1979
2220
|
}
|
|
@@ -1983,7 +2224,7 @@ var ZAI_DEFAULT_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
|
1983
2224
|
var ZAI_DEFAULT_MODEL = "glm-5.3";
|
|
1984
2225
|
var ZAI_DEFAULT_THINKING_BUDGET = 2048;
|
|
1985
2226
|
function zaiThinkingBudget() {
|
|
1986
|
-
const raw = process.env["
|
|
2227
|
+
const raw = process.env["ZERO_ZAI_THINKING_BUDGET"];
|
|
1987
2228
|
if (raw == null || raw.trim().length === 0)
|
|
1988
2229
|
return ZAI_DEFAULT_THINKING_BUDGET;
|
|
1989
2230
|
const n = Number.parseInt(raw, 10);
|
|
@@ -1998,18 +2239,18 @@ var CODEX_OAUTH_ISSUER = "https://auth.openai.com";
|
|
|
1998
2239
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1999
2240
|
var CODEX_DEFAULT_MODEL = "gpt-5.5";
|
|
2000
2241
|
var LOOP_SERVER_COMPACTION_TOKENS = 15e4;
|
|
2001
|
-
var PROCESS_SESSION_ID = `
|
|
2242
|
+
var PROCESS_SESSION_ID = `0-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;
|
|
2002
2243
|
var chatGptCodexAuthStates = /* @__PURE__ */ new Map();
|
|
2003
2244
|
function codexAuthStateKey(state) {
|
|
2004
2245
|
return JSON.stringify([state.authFilePath, state.accountId, state.refreshToken || state.accessToken]);
|
|
2005
2246
|
}
|
|
2006
2247
|
function readChatGptCodexEnv(env2 = process.env) {
|
|
2007
|
-
const access = env2["
|
|
2008
|
-
const refresh = env2["
|
|
2248
|
+
const access = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
|
|
2249
|
+
const refresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
|
|
2009
2250
|
if ((!access || access.length === 0) && (!refresh || refresh.length === 0)) {
|
|
2010
2251
|
return void 0;
|
|
2011
2252
|
}
|
|
2012
|
-
const accountId = env2["
|
|
2253
|
+
const accountId = env2["ZERO_CHATGPT_ACCOUNT_ID"];
|
|
2013
2254
|
return {
|
|
2014
2255
|
accessToken: access && access.length > 0 ? access : void 0,
|
|
2015
2256
|
refreshToken: refresh && refresh.length > 0 ? refresh : void 0,
|
|
@@ -2017,7 +2258,7 @@ function readChatGptCodexEnv(env2 = process.env) {
|
|
|
2017
2258
|
};
|
|
2018
2259
|
}
|
|
2019
2260
|
function resolveChatGptCodexAuthPath(env2 = process.env) {
|
|
2020
|
-
return env2["
|
|
2261
|
+
return env2["ZERO_CHATGPT_AUTH_FILE"] ?? join2(env2.HOME ?? homedir(), ".codex", "auth.json");
|
|
2021
2262
|
}
|
|
2022
2263
|
function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
|
|
2023
2264
|
try {
|
|
@@ -2046,7 +2287,7 @@ function persistChatGptCodexAuthFile(authPath, tokens, usedRefreshToken) {
|
|
|
2046
2287
|
`, { mode: 384 });
|
|
2047
2288
|
renameSync(tmp, authPath);
|
|
2048
2289
|
} catch (err) {
|
|
2049
|
-
process.stderr.write(`[
|
|
2290
|
+
process.stderr.write(`[0] warning: could not persist rotated Codex refresh token to ${authPath}: ${err instanceof Error ? err.message : String(err)}
|
|
2050
2291
|
`);
|
|
2051
2292
|
}
|
|
2052
2293
|
}
|
|
@@ -2082,6 +2323,7 @@ function accessTokenExpiryMs(accessToken) {
|
|
|
2082
2323
|
}
|
|
2083
2324
|
async function refreshChatGptCodexAccessToken(refreshToken) {
|
|
2084
2325
|
const res = await fetch(`${CODEX_OAUTH_ISSUER}/oauth/token`, {
|
|
2326
|
+
signal: AbortSignal.timeout(3e4),
|
|
2085
2327
|
method: "POST",
|
|
2086
2328
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2087
2329
|
body: new URLSearchParams({
|
|
@@ -2133,12 +2375,15 @@ function extractChatGptAccountId(tokens) {
|
|
|
2133
2375
|
}
|
|
2134
2376
|
return void 0;
|
|
2135
2377
|
}
|
|
2378
|
+
async function getChatGptCodexAccessToken(env2 = process.env) {
|
|
2379
|
+
return refreshChatGptCodexAuthState(resolveChatGptCodexAuthState(env2));
|
|
2380
|
+
}
|
|
2136
2381
|
function resolveChatGptCodexAuthState(env2) {
|
|
2137
2382
|
const fromEnvOnly = readChatGptCodexEnv(env2);
|
|
2138
2383
|
const fromFile = fromEnvOnly ? void 0 : readChatGptCodexAuthFile(env2);
|
|
2139
2384
|
const tokens = fromEnvOnly ?? fromFile;
|
|
2140
2385
|
if (!tokens) {
|
|
2141
|
-
throw new Error("ChatGPT Codex auth: neither
|
|
2386
|
+
throw new Error("ChatGPT Codex auth: neither ZERO_CHATGPT_ACCESS_TOKEN nor ZERO_CHATGPT_OAUTH_REFRESH_TOKEN is set. Run `codex login` and either forward the access token via worker-controller (preferred for multi-sandbox dispatch \u2014 avoids the OAuth refresh-token rotation race) or keep a valid ~/.codex/auth.json on this host.");
|
|
2142
2387
|
}
|
|
2143
2388
|
const identity = {
|
|
2144
2389
|
refreshToken: tokens.refreshToken ?? "",
|
|
@@ -2198,8 +2443,8 @@ function geminiAuthStateKey(refreshToken, accessToken) {
|
|
|
2198
2443
|
return JSON.stringify([refreshToken, refreshToken ? void 0 : accessToken]);
|
|
2199
2444
|
}
|
|
2200
2445
|
function readGeminiCodeAssistEnv(env2 = process.env) {
|
|
2201
|
-
const access = env2["
|
|
2202
|
-
const refresh = env2["
|
|
2446
|
+
const access = env2["ZERO_GEMINI_ACCESS_TOKEN"];
|
|
2447
|
+
const refresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
|
|
2203
2448
|
if ((!access || access.length === 0) && (!refresh || refresh.length === 0))
|
|
2204
2449
|
return void 0;
|
|
2205
2450
|
return {
|
|
@@ -2210,7 +2455,7 @@ function readGeminiCodeAssistEnv(env2 = process.env) {
|
|
|
2210
2455
|
function resolveGeminiCodeAssistAuthState(env2) {
|
|
2211
2456
|
const tokens = readGeminiCodeAssistEnv(env2);
|
|
2212
2457
|
if (!tokens) {
|
|
2213
|
-
throw new Error("Google Gemini Code Assist auth: neither
|
|
2458
|
+
throw new Error("Google Gemini Code Assist auth: neither ZERO_GEMINI_ACCESS_TOKEN nor ZERO_GEMINI_OAUTH_REFRESH_TOKEN is set. Sign in with your Google account (0 connect) or forward a fresh access token.");
|
|
2214
2459
|
}
|
|
2215
2460
|
const key = geminiAuthStateKey(tokens.refreshToken ?? "", tokens.accessToken);
|
|
2216
2461
|
const existing = geminiCodeAssistAuthStates.get(key);
|
|
@@ -2323,7 +2568,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
|
|
|
2323
2568
|
return state.inflightProjectResolve;
|
|
2324
2569
|
state.inflightProjectResolve = (async () => {
|
|
2325
2570
|
try {
|
|
2326
|
-
const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "
|
|
2571
|
+
const override = firstNonEmptyEnv(env2, "GOOGLE_CLOUD_PROJECT", "ZERO_GEMINI_PROJECT");
|
|
2327
2572
|
const accessToken = await refreshGeminiCodeAssistAuthState(state);
|
|
2328
2573
|
let load;
|
|
2329
2574
|
try {
|
|
@@ -2335,7 +2580,7 @@ async function resolveGeminiCodeAssistProject(state, env2, sleep = (ms) => new P
|
|
|
2335
2580
|
if (err.securityPolicyViolated) {
|
|
2336
2581
|
if (override)
|
|
2337
2582
|
return override;
|
|
2338
|
-
throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or
|
|
2583
|
+
throw new Error("Google Gemini Code Assist: this account is behind a VPC Service Controls perimeter \u2014 set GOOGLE_CLOUD_PROJECT (or ZERO_GEMINI_PROJECT).");
|
|
2339
2584
|
}
|
|
2340
2585
|
throw err;
|
|
2341
2586
|
}
|
|
@@ -2447,13 +2692,13 @@ function providerForModel(model, env2) {
|
|
|
2447
2692
|
return env2.OPENCODE_API_KEY ? "opencode" : void 0;
|
|
2448
2693
|
}
|
|
2449
2694
|
if (m.startsWith("copilot/")) {
|
|
2450
|
-
return env2["
|
|
2695
|
+
return env2["ZERO_COPILOT_GITHUB_TOKEN"] ? "copilot" : void 0;
|
|
2451
2696
|
}
|
|
2452
2697
|
if (m.startsWith("gemini") || m.startsWith("google/")) {
|
|
2453
|
-
return env2["
|
|
2698
|
+
return env2["ZERO_GEMINI_ACCESS_TOKEN"] || env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"] ? "google" : void 0;
|
|
2454
2699
|
}
|
|
2455
2700
|
if (/^gpt-|^o[1-4](?:[-_]|$)/.test(m)) {
|
|
2456
|
-
if (env2["
|
|
2701
|
+
if (env2["ZERO_CHATGPT_ACCESS_TOKEN"] || env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"])
|
|
2457
2702
|
return "chatgpt-codex";
|
|
2458
2703
|
if (env2.OPENAI_API_KEY)
|
|
2459
2704
|
return "openai";
|
|
@@ -2489,21 +2734,21 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2489
2734
|
if (configProvider !== void 0 && !Object.hasOwn(DEFAULT_PROVIDER_MODELS, configProvider)) {
|
|
2490
2735
|
throw new Error(`RuntimeConfig.provider is unsupported: ${configProvider}`);
|
|
2491
2736
|
}
|
|
2492
|
-
const selectedProviderRaw = configProvider ?? env2["
|
|
2493
|
-
const forcedProviderRaw = env2["
|
|
2737
|
+
const selectedProviderRaw = configProvider ?? env2["ZERO_SELECTED_PROVIDER"]?.trim();
|
|
2738
|
+
const forcedProviderRaw = env2["ZERO_FORCE_PROVIDER"]?.trim() || void 0;
|
|
2494
2739
|
if (selectedProviderRaw && forcedProviderRaw && selectedProviderRaw !== forcedProviderRaw) {
|
|
2495
|
-
throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "
|
|
2740
|
+
throw new Error(`${configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER"} conflicts with ZERO_FORCE_PROVIDER`);
|
|
2496
2741
|
}
|
|
2497
|
-
const primaryModel = env2["
|
|
2742
|
+
const primaryModel = env2["ZERO_MODEL"]?.trim();
|
|
2498
2743
|
const selectedProviderApplies = configProvider !== void 0 || !preferredModel || !primaryModel || preferredModel === primaryModel;
|
|
2499
2744
|
const pinnedProviderRaw = forcedProviderRaw ?? (selectedProviderApplies ? selectedProviderRaw : void 0);
|
|
2500
2745
|
if (pinnedProviderRaw) {
|
|
2501
|
-
const source = pinnedProviderRaw === forcedProviderRaw ? "
|
|
2746
|
+
const source = pinnedProviderRaw === forcedProviderRaw ? "ZERO_FORCE_PROVIDER" : configProvider !== void 0 ? "RuntimeConfig.provider" : "ZERO_SELECTED_PROVIDER";
|
|
2502
2747
|
if (!Object.hasOwn(DEFAULT_PROVIDER_MODELS, pinnedProviderRaw)) {
|
|
2503
2748
|
throw new Error(`${source} is unsupported: ${pinnedProviderRaw}`);
|
|
2504
2749
|
}
|
|
2505
2750
|
const provider = pinnedProviderRaw;
|
|
2506
|
-
const model = preferredModel ?? env2["
|
|
2751
|
+
const model = preferredModel ?? env2["ZERO_MODEL"] ?? (configProvider !== void 0 || provider === "hosted" ? DEFAULT_PROVIDER_MODELS[provider] : void 0);
|
|
2507
2752
|
if (model === void 0 || model === "" && provider !== "hosted") {
|
|
2508
2753
|
throw new Error(`${source} requires an explicit model`);
|
|
2509
2754
|
}
|
|
@@ -2613,7 +2858,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2613
2858
|
case "copilot":
|
|
2614
2859
|
return {
|
|
2615
2860
|
provider: "copilot",
|
|
2616
|
-
apiKey: env2["
|
|
2861
|
+
apiKey: env2["ZERO_COPILOT_GITHUB_TOKEN"],
|
|
2617
2862
|
baseUrl: env2.COPILOT_BASE_URL ?? COPILOT_API_BASE,
|
|
2618
2863
|
defaultModel: preferredModel ?? COPILOT_DEFAULT_MODEL,
|
|
2619
2864
|
wireApi: "chat_completions"
|
|
@@ -2631,7 +2876,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2631
2876
|
provider: "chatgpt-codex",
|
|
2632
2877
|
apiKey: "",
|
|
2633
2878
|
baseUrl: CODEX_API_ENDPOINT,
|
|
2634
|
-
defaultModel: env2["
|
|
2879
|
+
defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
|
|
2635
2880
|
wireApi: "responses"
|
|
2636
2881
|
};
|
|
2637
2882
|
case "anthropic":
|
|
@@ -2661,8 +2906,23 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2661
2906
|
default:
|
|
2662
2907
|
break;
|
|
2663
2908
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2909
|
+
try {
|
|
2910
|
+
const hostedCreds = loadCloudCredentials({
|
|
2911
|
+
env: env2,
|
|
2912
|
+
warn: () => {
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
return {
|
|
2916
|
+
provider: "hosted",
|
|
2917
|
+
apiKey: hostedCreds.token,
|
|
2918
|
+
baseUrl: `${hostedCreds.host}/api/inference/v1`,
|
|
2919
|
+
defaultModel: "",
|
|
2920
|
+
wireApi: "chat_completions"
|
|
2921
|
+
};
|
|
2922
|
+
} catch {
|
|
2923
|
+
}
|
|
2924
|
+
const chatGptAccess = env2["ZERO_CHATGPT_ACCESS_TOKEN"];
|
|
2925
|
+
const chatGptRefresh = env2["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"];
|
|
2666
2926
|
const chatGptAuthFile = !chatGptAccess && !chatGptRefresh ? readChatGptCodexAuthFile(env2) : void 0;
|
|
2667
2927
|
if (chatGptAccess && chatGptAccess.length > 0 || chatGptRefresh && chatGptRefresh.length > 0 || !!chatGptAuthFile) {
|
|
2668
2928
|
return {
|
|
@@ -2675,7 +2935,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2675
2935
|
// baseUrl is informational only — the runtime hardcodes
|
|
2676
2936
|
// CODEX_API_ENDPOINT for this provider.
|
|
2677
2937
|
baseUrl: CODEX_API_ENDPOINT,
|
|
2678
|
-
defaultModel: env2["
|
|
2938
|
+
defaultModel: env2["ZERO_MODEL"] ?? CODEX_DEFAULT_MODEL,
|
|
2679
2939
|
wireApi: "responses"
|
|
2680
2940
|
};
|
|
2681
2941
|
}
|
|
@@ -2771,7 +3031,7 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2771
3031
|
wireApi: opencodeWireApiForModel(preferredModel)
|
|
2772
3032
|
};
|
|
2773
3033
|
}
|
|
2774
|
-
const copilotToken = env2["
|
|
3034
|
+
const copilotToken = env2["ZERO_COPILOT_GITHUB_TOKEN"];
|
|
2775
3035
|
if (copilotToken) {
|
|
2776
3036
|
return {
|
|
2777
3037
|
provider: "copilot",
|
|
@@ -2781,14 +3041,14 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2781
3041
|
wireApi: "chat_completions"
|
|
2782
3042
|
};
|
|
2783
3043
|
}
|
|
2784
|
-
const geminiAccess = env2["
|
|
2785
|
-
const geminiRefresh = env2["
|
|
3044
|
+
const geminiAccess = env2["ZERO_GEMINI_ACCESS_TOKEN"];
|
|
3045
|
+
const geminiRefresh = env2["ZERO_GEMINI_OAUTH_REFRESH_TOKEN"];
|
|
2786
3046
|
if (geminiAccess && geminiAccess.length > 0 || geminiRefresh && geminiRefresh.length > 0) {
|
|
2787
3047
|
return {
|
|
2788
3048
|
provider: "google",
|
|
2789
3049
|
apiKey: "",
|
|
2790
3050
|
baseUrl: CODE_ASSIST_ENDPOINT,
|
|
2791
|
-
defaultModel: env2["
|
|
3051
|
+
defaultModel: env2["ZERO_MODEL"] ?? GEMINI_DEFAULT_MODEL,
|
|
2792
3052
|
wireApi: "google_generate_content"
|
|
2793
3053
|
};
|
|
2794
3054
|
}
|
|
@@ -2802,21 +3062,6 @@ function detectProvider(configApiKey, preferredModel, env2, configProvider) {
|
|
|
2802
3062
|
wireApi: "chat_completions"
|
|
2803
3063
|
};
|
|
2804
3064
|
}
|
|
2805
|
-
try {
|
|
2806
|
-
const hostedCreds = loadCloudCredentials({
|
|
2807
|
-
env: env2,
|
|
2808
|
-
warn: () => {
|
|
2809
|
-
}
|
|
2810
|
-
});
|
|
2811
|
-
return {
|
|
2812
|
-
provider: "hosted",
|
|
2813
|
-
apiKey: hostedCreds.token,
|
|
2814
|
-
baseUrl: `${hostedCreds.host}/api/inference/v1`,
|
|
2815
|
-
defaultModel: "",
|
|
2816
|
-
wireApi: "chat_completions"
|
|
2817
|
-
};
|
|
2818
|
-
} catch {
|
|
2819
|
-
}
|
|
2820
3065
|
return {
|
|
2821
3066
|
provider: "anthropic",
|
|
2822
3067
|
apiKey: "",
|
|
@@ -2843,7 +3088,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2843
3088
|
reasoningEffort;
|
|
2844
3089
|
azureConfig;
|
|
2845
3090
|
serverCompactionTokens;
|
|
2846
|
-
/** Ordered fallback chain (
|
|
3091
|
+
/** Ordered fallback chain (ZERO_LLM_FALLBACK). Empty = no failover. */
|
|
2847
3092
|
fallbackChain;
|
|
2848
3093
|
/** Index into fallbackChain — which entry to try next. */
|
|
2849
3094
|
fallbackIndex;
|
|
@@ -2919,7 +3164,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2919
3164
|
credentials: resolveFailoverProvider(entry.provider, entry.model, this.env)
|
|
2920
3165
|
}));
|
|
2921
3166
|
this.fallbackIndex = 0;
|
|
2922
|
-
const detected = detectProvider(config.apiKey, config.model ?? this.env["
|
|
3167
|
+
const detected = detectProvider(config.apiKey, config.model ?? this.env["ZERO_MODEL"], this.env, config.provider);
|
|
2923
3168
|
this.provider = detected.provider;
|
|
2924
3169
|
this.apiKey = detected.apiKey;
|
|
2925
3170
|
this.baseUrl = detected.baseUrl;
|
|
@@ -2936,9 +3181,9 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2936
3181
|
this.geminiAuthState = resolveGeminiCodeAssistAuthState(this.env);
|
|
2937
3182
|
}
|
|
2938
3183
|
}
|
|
2939
|
-
this.reasoningEffort = this.env["
|
|
3184
|
+
this.reasoningEffort = this.env["ZERO_REASONING_EFFORT"] ?? detected.reasoningEffort;
|
|
2940
3185
|
this.serverCompactionTokens = config.serverCompactionTokens !== void 0 ? Math.max(1e3, config.serverCompactionTokens) : void 0;
|
|
2941
|
-
const requestedModel = config.model ?? this.env["
|
|
3186
|
+
const requestedModel = config.model ?? this.env["ZERO_MODEL"];
|
|
2942
3187
|
if (requestedModel === "free" && this.provider === "openrouter") {
|
|
2943
3188
|
this.model = FREE_OPENROUTER_MODEL;
|
|
2944
3189
|
} else {
|
|
@@ -2951,11 +3196,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2951
3196
|
this.model = copilotModelId(this.model);
|
|
2952
3197
|
}
|
|
2953
3198
|
this.applyModelWireApi();
|
|
2954
|
-
if (this.apiKey && !this.env["
|
|
3199
|
+
if (this.apiKey && !this.env["ZERO_SKIP_PROVIDER_BANNER"]) {
|
|
2955
3200
|
void logProviderStartup(this.provider, this.providerLabel, this.baseUrl, this.model, this.wireApi, this.apiKey).catch(() => {
|
|
2956
3201
|
});
|
|
2957
3202
|
}
|
|
2958
3203
|
}
|
|
3204
|
+
/** Discover models using this runtime's captured account, including after a separate login changes. */
|
|
3205
|
+
async codexModelCatalog(signal) {
|
|
3206
|
+
const state = this.codexAuthState;
|
|
3207
|
+
if (this.provider !== "chatgpt-codex" || !state)
|
|
3208
|
+
throw new Error("No active Codex subscription");
|
|
3209
|
+
const { loadCodexModelCatalog } = await import("./codex-models-TYYV4JNN.js");
|
|
3210
|
+
return loadCodexModelCatalog({ signal, resolveCredentials: () => refreshChatGptCodexAuthState(state) });
|
|
3211
|
+
}
|
|
3212
|
+
/** Check account admission and resolve the service model without inference.
|
|
3213
|
+
* Each explicit check refreshes admission; failed discovery is never cached.
|
|
3214
|
+
*/
|
|
3215
|
+
async prepare() {
|
|
3216
|
+
while (this.provider === "hosted") {
|
|
3217
|
+
const config = this.config;
|
|
3218
|
+
const client = new CloudClient({
|
|
3219
|
+
host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
|
|
3220
|
+
token: this.apiKey
|
|
3221
|
+
});
|
|
3222
|
+
try {
|
|
3223
|
+
const account = await client.getInferenceAccount();
|
|
3224
|
+
if (this.config !== config)
|
|
3225
|
+
continue;
|
|
3226
|
+
if (!account) {
|
|
3227
|
+
throw new CloudError("0cloud account availability could not be read. Check again or review your account in /connect.", void 0, "/api/inference/account", "unsupported_account_data");
|
|
3228
|
+
}
|
|
3229
|
+
if (!account.admission.eligible) {
|
|
3230
|
+
const reason = account.admission.reason ?? account.reason ?? "account_restricted";
|
|
3231
|
+
throw new CloudError(account.state === "unavailable" ? `0cloud account availability could not be checked (${reason}). Check again or review your account in /connect.` : `0cloud account access is restricted (${reason}). Review your account in /connect or contact your organization owner.`, void 0, "/api/inference/account", reason);
|
|
3232
|
+
}
|
|
3233
|
+
await this.ensureHostedModel();
|
|
3234
|
+
if (this.config === config)
|
|
3235
|
+
return;
|
|
3236
|
+
} catch (error) {
|
|
3237
|
+
if (this.config !== config)
|
|
3238
|
+
continue;
|
|
3239
|
+
throw error;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
2959
3243
|
/**
|
|
2960
3244
|
* Mutate the live selection in place so the NEXT turn (the engine reads
|
|
2961
3245
|
* `config.runtime` per turn) and the NEXT `forkForSubagent` pick up the new
|
|
@@ -2964,11 +3248,12 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2964
3248
|
*/
|
|
2965
3249
|
reconfigure(sel) {
|
|
2966
3250
|
const providerChanged = sel.provider !== void 0 && sel.provider !== this.provider;
|
|
2967
|
-
if (providerChanged) {
|
|
3251
|
+
if (providerChanged || this.provider === "hosted" && (sel.env !== void 0 || sel.provider === "hosted")) {
|
|
2968
3252
|
const merged = {
|
|
2969
3253
|
...this.config,
|
|
3254
|
+
...providerChanged && sel.provider === "hosted" && sel.model === void 0 ? { model: "" } : {},
|
|
2970
3255
|
apiKey: void 0,
|
|
2971
|
-
provider: sel.provider,
|
|
3256
|
+
provider: sel.provider ?? this.provider,
|
|
2972
3257
|
...sel.model !== void 0 ? { model: sel.model } : {},
|
|
2973
3258
|
...sel.agentModels !== void 0 ? { agentModels: sel.agentModels } : {},
|
|
2974
3259
|
...sel.singleModel !== void 0 ? { singleModel: sel.singleModel } : {},
|
|
@@ -2998,6 +3283,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
2998
3283
|
this.wireApi = openAICompatibleWireApi(this.env, "AZURE_OPENAI_WIRE_API", this.azureConfig.wireApi);
|
|
2999
3284
|
this.applyModelWireApi();
|
|
3000
3285
|
this.reasoningEffort = void 0;
|
|
3286
|
+
this.hostedCatalogPromise = null;
|
|
3287
|
+
this.hostedMaxOutputTokens = void 0;
|
|
3001
3288
|
}
|
|
3002
3289
|
}
|
|
3003
3290
|
}
|
|
@@ -3082,25 +3369,50 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3082
3369
|
}
|
|
3083
3370
|
/** The server catalog is authoritative even when a model was selected explicitly. */
|
|
3084
3371
|
async ensureHostedModel() {
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3372
|
+
while (this.provider === "hosted") {
|
|
3373
|
+
let pending = this.hostedCatalogPromise;
|
|
3374
|
+
if (!pending) {
|
|
3375
|
+
const requestedModel = this.model;
|
|
3089
3376
|
const client = new CloudClient({
|
|
3090
3377
|
host: this.baseUrl.replace(/\/api\/inference\/v1$/, ""),
|
|
3091
3378
|
token: this.apiKey
|
|
3092
3379
|
});
|
|
3093
|
-
const
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3380
|
+
const request = client.getInferenceModels().then((catalog) => {
|
|
3381
|
+
if (this.hostedCatalogPromise !== request)
|
|
3382
|
+
return;
|
|
3383
|
+
const selected = requestedModel ? catalog.data.find((model) => model.id === requestedModel) : catalog.data[0];
|
|
3384
|
+
if (!selected) {
|
|
3385
|
+
throw new Error(requestedModel ? `Hosted model "${requestedModel}" is unavailable. Run \`0 models\` for available models.` : "No hosted models are available. Run `0 models` to check service availability.");
|
|
3386
|
+
}
|
|
3387
|
+
this.model = selected.id;
|
|
3388
|
+
this.wireApi = selected.wire_api;
|
|
3389
|
+
this.hostedMaxOutputTokens = selected.max_output_tokens;
|
|
3390
|
+
});
|
|
3391
|
+
this.hostedCatalogPromise = pending = request;
|
|
3392
|
+
}
|
|
3393
|
+
try {
|
|
3394
|
+
await pending;
|
|
3395
|
+
} catch (error) {
|
|
3396
|
+
if (this.hostedCatalogPromise !== pending)
|
|
3397
|
+
continue;
|
|
3398
|
+
this.hostedCatalogPromise = null;
|
|
3399
|
+
throw error;
|
|
3400
|
+
}
|
|
3401
|
+
if (this.hostedCatalogPromise === pending)
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
/** All audits and nested workers on this hosted credential share admission. */
|
|
3406
|
+
async acquireHostedSlot(signal) {
|
|
3407
|
+
while (this.provider === "hosted") {
|
|
3408
|
+
const config = this.config;
|
|
3409
|
+
const release = await acquireHostedRequestSlot(this.baseUrl, this.apiKey, signal);
|
|
3410
|
+
if (this.config === config)
|
|
3411
|
+
return release;
|
|
3412
|
+
release();
|
|
3413
|
+
await this.ensureHostedModel();
|
|
3102
3414
|
}
|
|
3103
|
-
|
|
3415
|
+
return void 0;
|
|
3104
3416
|
}
|
|
3105
3417
|
/**
|
|
3106
3418
|
* A hard dollar ceiling needs a provider-enforced bound on the next response.
|
|
@@ -3178,8 +3490,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3178
3490
|
if (this.provider === "chatgpt-codex") {
|
|
3179
3491
|
return {
|
|
3180
3492
|
"Content-Type": "application/json",
|
|
3181
|
-
originator: "
|
|
3182
|
-
"User-Agent": `
|
|
3493
|
+
originator: "0",
|
|
3494
|
+
"User-Agent": `0/${VERSION}`
|
|
3183
3495
|
};
|
|
3184
3496
|
}
|
|
3185
3497
|
if (this.isGeminiCodeAssist) {
|
|
@@ -3211,8 +3523,8 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3211
3523
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3212
3524
|
}
|
|
3213
3525
|
if (this.provider === "openrouter") {
|
|
3214
|
-
headers["HTTP-Referer"] = "https://
|
|
3215
|
-
headers["X-Title"] = "
|
|
3526
|
+
headers["HTTP-Referer"] = "https://0.security";
|
|
3527
|
+
headers["X-Title"] = "0 Security Scanner";
|
|
3216
3528
|
}
|
|
3217
3529
|
return headers;
|
|
3218
3530
|
}
|
|
@@ -3236,7 +3548,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3236
3548
|
* happens.
|
|
3237
3549
|
*
|
|
3238
3550
|
* session_id is process-stable (PROCESS_SESSION_ID, randomised
|
|
3239
|
-
* once at module load). A
|
|
3551
|
+
* once at module load). A @0/cli invocation = one scan = one
|
|
3240
3552
|
* session, so the process-lifetime constant is the right
|
|
3241
3553
|
* granularity. If we ever want per-scan ids inside a long-lived
|
|
3242
3554
|
* controller process, add a setter on the runtime; for now this
|
|
@@ -3390,7 +3702,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3390
3702
|
/**
|
|
3391
3703
|
* Per-turn prompt-cache accounting line, so a run can be shown to actually
|
|
3392
3704
|
* be hitting cache rather than assumed to be. Off unless
|
|
3393
|
-
* `
|
|
3705
|
+
* `ZERO_DEBUG_PROMPT_CACHE` is set — this fires once per agent turn, and an
|
|
3394
3706
|
* unconditional line would interleave with the TUI on every scan.
|
|
3395
3707
|
*
|
|
3396
3708
|
* The same numbers reach the cloud without this flag: `cachedInputTokens`
|
|
@@ -3398,7 +3710,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3398
3710
|
* is the durable, queryable proof. This is the local fast path.
|
|
3399
3711
|
*/
|
|
3400
3712
|
logCacheUsage(usage) {
|
|
3401
|
-
if (!usage || !process.env["
|
|
3713
|
+
if (!usage || !process.env["ZERO_DEBUG_PROMPT_CACHE"])
|
|
3402
3714
|
return;
|
|
3403
3715
|
const read = usage.cachedInputTokens ?? 0;
|
|
3404
3716
|
const write = usage.cacheWriteTokens ?? 0;
|
|
@@ -3442,11 +3754,11 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3442
3754
|
case "google":
|
|
3443
3755
|
return "Google Gemini (Code Assist)";
|
|
3444
3756
|
case "hosted":
|
|
3445
|
-
return "
|
|
3757
|
+
return "0.security Cloud";
|
|
3446
3758
|
}
|
|
3447
3759
|
}
|
|
3448
3760
|
noKeyError() {
|
|
3449
|
-
return "No provider credential found. Set one of:\n env
|
|
3761
|
+
return "No provider credential found. Set one of:\n env ZERO_CHATGPT_OAUTH_REFRESH_TOKEN=... 0 <command> (ChatGPT Codex subscription auth)\n export OPENROUTER_API_KEY=sk-or-... (OpenRouter \u2014 many models, one key)\n export DEEPSEEK_API_KEY=... (DeepSeek \u2014 direct Flash 0731 inference)\n export ANTHROPIC_API_KEY=sk-ant-... (Anthropic \u2014 direct Claude access)\n export AZURE_OPENAI_API_KEY=... (Azure OpenAI \u2014 reuse your Codex Azure provider)\n export OPENAI_API_KEY=sk-... (OpenAI \u2014 direct GPT access)\n export Z_AI_API_KEY=... (Z.ai GLM \u2014 flat-rate Coding Plan, Anthropic-compatible)\n export KIMI_API_KEY=... (Moonshot Kimi K3 \u2014 flat-rate coding, Anthropic-compatible)\n export QWEN_API_KEY=... (Alibaba Qwen \u2014 Token Plan sub, OpenAI-compatible)\n export XAI_API_KEY=... (xAI Grok \u2014 OpenAI-compatible)\n export OPENCODE_API_KEY=... (OpenCode Zen \u2014 multi-wire gateway)\n export ZERO_COPILOT_GITHUB_TOKEN=... (GitHub Copilot \u2014 device-code OAuth token)\n Run `0 login` (0 hosted inference)";
|
|
3450
3762
|
}
|
|
3451
3763
|
getConfigurationDiagnostics() {
|
|
3452
3764
|
if (!this.apiKey && this.provider !== "chatgpt-codex") {
|
|
@@ -3466,7 +3778,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3466
3778
|
};
|
|
3467
3779
|
}
|
|
3468
3780
|
const hasConfiguredBaseUrl = !!(this.env.AZURE_OPENAI_BASE_URL || this.env.OPENAI_BASE_URL || this.azureConfig.baseUrl);
|
|
3469
|
-
const hasConfiguredModel = !!(this.config.model || this.env["
|
|
3781
|
+
const hasConfiguredModel = !!(this.config.model || this.env["ZERO_MODEL"] || this.env.AZURE_OPENAI_MODEL || this.azureConfig.model);
|
|
3470
3782
|
const missing = [];
|
|
3471
3783
|
if (!hasConfiguredBaseUrl) {
|
|
3472
3784
|
missing.push("AZURE_OPENAI_BASE_URL (or [model_providers.azure].base_url in ~/.codex/config.toml)");
|
|
@@ -3482,7 +3794,7 @@ var LlmApiRuntime = class _LlmApiRuntime {
|
|
|
3482
3794
|
reason: "invalid_config",
|
|
3483
3795
|
fatalError: `Azure OpenAI runtime is selected, but the configuration is incomplete.
|
|
3484
3796
|
Missing: ${missing.join("; ")}
|
|
3485
|
-
|
|
3797
|
+
0 will not guess Azure defaults because that can silently route to the wrong endpoint or deployment.`
|
|
3486
3798
|
};
|
|
3487
3799
|
}
|
|
3488
3800
|
return {
|
|
@@ -3500,15 +3812,15 @@ Missing: ${missing.join("; ")}
|
|
|
3500
3812
|
*
|
|
3501
3813
|
* Two 429 classes are handled differently:
|
|
3502
3814
|
* - per-minute rate limit → retry with the wider 429 budget
|
|
3503
|
-
* (
|
|
3815
|
+
* (ZERO_LLM_429_MAX_RETRIES attempts / ZERO_LLM_429_MAX_RETRY_WAIT_MS
|
|
3504
3816
|
* cumulative, defaults 12 / 5min) since the limiter resets every ~60s;
|
|
3505
3817
|
* `Retry-After` / `retry-after-ms` headers are honored up to a 120s cap.
|
|
3506
3818
|
* - plan-quota exhaustion (`usage_limit_reached`, resets in hours/days) →
|
|
3507
|
-
* skips retries and immediately advances `
|
|
3819
|
+
* skips retries and immediately advances `ZERO_LLM_FALLBACK`; if no
|
|
3508
3820
|
* configured fallback has credentials, it throws QuotaExhaustedError.
|
|
3509
3821
|
*
|
|
3510
3822
|
* Other retryable statuses (transient 5xx) keep the generic budget:
|
|
3511
|
-
*
|
|
3823
|
+
* ZERO_LLM_MAX_RETRIES (attempts) and ZERO_LLM_MAX_RETRY_WAIT_MS
|
|
3512
3824
|
* (cumulative backoff). On exhaustion it returns the last still-failing
|
|
3513
3825
|
* Response with its body intact, so the caller's existing `!res.ok` branch
|
|
3514
3826
|
* surfaces the clear "API error <status>" message — a rate-limit never
|
|
@@ -3519,7 +3831,7 @@ Missing: ${missing.join("; ")}
|
|
|
3519
3831
|
* is fixed across attempts.
|
|
3520
3832
|
*/
|
|
3521
3833
|
/**
|
|
3522
|
-
* Try the next fallback provider in the chain (
|
|
3834
|
+
* Try the next fallback provider in the chain (ZERO_LLM_FALLBACK).
|
|
3523
3835
|
* Updates `this.provider`, `this.model`, `this.apiKey`, `this.baseUrl`,
|
|
3524
3836
|
* `this.wireApi` to match the next valid provider. Returns `true` when a
|
|
3525
3837
|
* valid next provider was found and switched to, `false` when the chain is
|
|
@@ -3531,7 +3843,7 @@ Missing: ${missing.join("; ")}
|
|
|
3531
3843
|
this.fallbackIndex++;
|
|
3532
3844
|
const cfg = entry.credentials;
|
|
3533
3845
|
if (!cfg) {
|
|
3534
|
-
diag.warn("failover_provider_skipped", `
|
|
3846
|
+
diag.warn("failover_provider_skipped", `ZERO_LLM_FALLBACK: skipping ${entry.provider} (auth env missing)`, { provider: entry.provider, model: entry.model, cause: "auth-env-missing" });
|
|
3535
3847
|
continue;
|
|
3536
3848
|
}
|
|
3537
3849
|
this.provider = entry.provider;
|
|
@@ -3577,7 +3889,7 @@ Missing: ${missing.join("; ")}
|
|
|
3577
3889
|
} catch (error) {
|
|
3578
3890
|
abort?.throwIfCancelled();
|
|
3579
3891
|
if (this.provider === "hosted") {
|
|
3580
|
-
throw new Error("
|
|
3892
|
+
throw new Error("0 hosted request outcome is unknown. Automatic replay is disabled; check your inference usage before retrying.", { cause: error });
|
|
3581
3893
|
}
|
|
3582
3894
|
const cause = error instanceof Error ? error.cause : void 0;
|
|
3583
3895
|
const causeCode = cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string" ? cause.code : "unknown";
|
|
@@ -3605,12 +3917,12 @@ Missing: ${missing.join("; ")}
|
|
|
3605
3917
|
if (res.ok || !isRetryableHttpStatus(res.status)) {
|
|
3606
3918
|
return res;
|
|
3607
3919
|
}
|
|
3608
|
-
if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-
|
|
3920
|
+
if (this.provider === "hosted" && res.status === 429 && res.headers.get("x-0-retry-safe") !== "1") {
|
|
3609
3921
|
return res;
|
|
3610
3922
|
}
|
|
3611
3923
|
if (this.provider === "hosted" && res.status >= 500) {
|
|
3612
3924
|
await res.body?.cancel();
|
|
3613
|
-
throw new Error(`
|
|
3925
|
+
throw new Error(`0 hosted request returned HTTP ${res.status}; its outcome may be unknown. Automatic replay is disabled; check your inference usage before retrying.`);
|
|
3614
3926
|
}
|
|
3615
3927
|
abort?.throwIfCancelled();
|
|
3616
3928
|
const is429 = res.status === 429;
|
|
@@ -3714,11 +4026,15 @@ Missing: ${missing.join("; ")}
|
|
|
3714
4026
|
};
|
|
3715
4027
|
}
|
|
3716
4028
|
const systemPrompt = context?.systemPrompt ?? "";
|
|
4029
|
+
let releaseHostedSlot = this.provider === "hosted" ? await this.acquireHostedSlot() : void 0;
|
|
3717
4030
|
const controller = new AbortController();
|
|
3718
4031
|
const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
|
|
3719
4032
|
try {
|
|
3720
4033
|
let res;
|
|
3721
4034
|
do {
|
|
4035
|
+
if (this.provider === "hosted" && !releaseHostedSlot) {
|
|
4036
|
+
releaseHostedSlot = await this.acquireHostedSlot(controller.signal);
|
|
4037
|
+
}
|
|
3722
4038
|
if (this.isOpenAICompat && this.wireApi === "chat_completions") {
|
|
3723
4039
|
const messages = [];
|
|
3724
4040
|
if (systemPrompt) {
|
|
@@ -3846,6 +4162,8 @@ Missing: ${missing.join("; ")}
|
|
|
3846
4162
|
durationMs: Date.now() - start,
|
|
3847
4163
|
error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
|
|
3848
4164
|
};
|
|
4165
|
+
} finally {
|
|
4166
|
+
releaseHostedSlot?.();
|
|
3849
4167
|
}
|
|
3850
4168
|
}
|
|
3851
4169
|
// ── Native Runtime interface (structured messages + tool_use) ──
|
|
@@ -3899,12 +4217,25 @@ Missing: ${missing.join("; ")}
|
|
|
3899
4217
|
}
|
|
3900
4218
|
if (signal?.aborted)
|
|
3901
4219
|
return this.cancelledResult(start);
|
|
4220
|
+
let releaseHostedSlot;
|
|
4221
|
+
if (this.provider === "hosted") {
|
|
4222
|
+
try {
|
|
4223
|
+
releaseHostedSlot = await this.acquireHostedSlot(signal);
|
|
4224
|
+
} catch (error) {
|
|
4225
|
+
if (signal?.aborted)
|
|
4226
|
+
return this.cancelledResult(start);
|
|
4227
|
+
throw error;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
3902
4230
|
const controller = new AbortController();
|
|
3903
4231
|
const timer = setTimeout(() => controller.abort(), this.config.timeout || 12e4);
|
|
3904
4232
|
const call = composeCallAbort(controller.signal, signal);
|
|
3905
4233
|
try {
|
|
3906
4234
|
let res;
|
|
3907
4235
|
do {
|
|
4236
|
+
if (this.provider === "hosted" && !releaseHostedSlot) {
|
|
4237
|
+
releaseHostedSlot = await this.acquireHostedSlot(call.signal);
|
|
4238
|
+
}
|
|
3908
4239
|
if (this.isOpenAICompat && this.wireApi === "chat_completions") {
|
|
3909
4240
|
const chatMessages = [];
|
|
3910
4241
|
chatMessages.push({ role: "system", content: system });
|
|
@@ -4095,6 +4426,7 @@ Missing: ${missing.join("; ")}
|
|
|
4095
4426
|
}
|
|
4096
4427
|
const streamed = await this.consumeResponsesStream(res, start, callbacks, {
|
|
4097
4428
|
idleTimeoutMs: llmStreamIdleTimeoutMs(),
|
|
4429
|
+
eventIdleTimeoutMs: llmStreamEventIdleTimeoutMs(),
|
|
4098
4430
|
abort: call
|
|
4099
4431
|
});
|
|
4100
4432
|
clearTimeout(timer);
|
|
@@ -4402,6 +4734,7 @@ Missing: ${missing.join("; ")}
|
|
|
4402
4734
|
error: timedOut ? `${this.providerLabel} API request timed out` : `${this.providerLabel} API error: ${msg}`
|
|
4403
4735
|
};
|
|
4404
4736
|
} finally {
|
|
4737
|
+
releaseHostedSlot?.();
|
|
4405
4738
|
call.dispose();
|
|
4406
4739
|
}
|
|
4407
4740
|
}
|
|
@@ -4416,10 +4749,14 @@ Missing: ${missing.join("; ")}
|
|
|
4416
4749
|
};
|
|
4417
4750
|
}
|
|
4418
4751
|
const idleTimeoutMs = opts?.idleTimeoutMs ?? llmStreamIdleTimeoutMs();
|
|
4752
|
+
const eventIdleTimeoutMs = opts?.eventIdleTimeoutMs ?? llmStreamEventIdleTimeoutMs();
|
|
4753
|
+
let lastEventAt = Date.now();
|
|
4419
4754
|
const operatorSignal = opts?.abort?.operator;
|
|
4420
4755
|
let stalled = false;
|
|
4756
|
+
let stallIdleMs = idleTimeoutMs;
|
|
4421
4757
|
const readBounded = async () => {
|
|
4422
4758
|
let timer;
|
|
4759
|
+
let eventTimer;
|
|
4423
4760
|
const detach = operatorSignal ? new AbortController() : void 0;
|
|
4424
4761
|
try {
|
|
4425
4762
|
return await Promise.race([
|
|
@@ -4427,9 +4764,22 @@ Missing: ${missing.join("; ")}
|
|
|
4427
4764
|
new Promise((_resolve, reject) => {
|
|
4428
4765
|
timer = setTimeout(() => {
|
|
4429
4766
|
stalled = true;
|
|
4767
|
+
stallIdleMs = idleTimeoutMs;
|
|
4430
4768
|
reject(new Error("stream stalled"));
|
|
4431
4769
|
}, idleTimeoutMs);
|
|
4432
4770
|
}),
|
|
4771
|
+
// Event-level racer: fires when no MEANINGFUL SSE event has arrived
|
|
4772
|
+
// for `eventIdleTimeoutMs`, even if keep-alive bytes keep resetting
|
|
4773
|
+
// the byte-level timer above. Recomputed per read, so an event that
|
|
4774
|
+
// landed while the previous chunk was being parsed re-arms it.
|
|
4775
|
+
new Promise((_resolve, reject) => {
|
|
4776
|
+
const remainingMs = eventIdleTimeoutMs - (Date.now() - lastEventAt);
|
|
4777
|
+
eventTimer = setTimeout(() => {
|
|
4778
|
+
stalled = true;
|
|
4779
|
+
stallIdleMs = eventIdleTimeoutMs;
|
|
4780
|
+
reject(new Error("stream stalled"));
|
|
4781
|
+
}, Math.max(remainingMs, 0));
|
|
4782
|
+
}),
|
|
4433
4783
|
// A real aborted `fetch` also errors the body stream, so `read()`
|
|
4434
4784
|
// would reject on its own — but only for a live socket. This racer
|
|
4435
4785
|
// is what makes cancellation immediate and unconditional, including
|
|
@@ -4445,16 +4795,21 @@ Missing: ${missing.join("; ")}
|
|
|
4445
4795
|
] : []
|
|
4446
4796
|
]);
|
|
4447
4797
|
} finally {
|
|
4448
|
-
|
|
4449
|
-
|
|
4798
|
+
clearTimeout(timer);
|
|
4799
|
+
clearTimeout(eventTimer);
|
|
4450
4800
|
detach?.abort();
|
|
4451
4801
|
}
|
|
4452
4802
|
};
|
|
4453
4803
|
const decoder = new TextDecoder();
|
|
4454
4804
|
let buffer = "";
|
|
4805
|
+
let trailingCR = false;
|
|
4806
|
+
let receivedBytes = 0;
|
|
4807
|
+
let malformedEvents = 0;
|
|
4808
|
+
const eventTypes = /* @__PURE__ */ new Set();
|
|
4809
|
+
const identifier = (value) => typeof value === "string" && /^[A-Za-z0-9_.:-]{1,96}$/.test(value) ? value : null;
|
|
4455
4810
|
let completedResponse = null;
|
|
4456
|
-
let
|
|
4457
|
-
let
|
|
4811
|
+
let streamFailure;
|
|
4812
|
+
let responseUsage;
|
|
4458
4813
|
const streamedOutputItems = [];
|
|
4459
4814
|
let thinkingText = "";
|
|
4460
4815
|
let lastThinkingEmit = 0;
|
|
@@ -4477,7 +4832,7 @@ Missing: ${missing.join("; ")}
|
|
|
4477
4832
|
lastThinkingLength = thinkingText.length;
|
|
4478
4833
|
callbacks.onThinking(thinkingText);
|
|
4479
4834
|
};
|
|
4480
|
-
while (true) {
|
|
4835
|
+
responses: while (true) {
|
|
4481
4836
|
let chunk;
|
|
4482
4837
|
try {
|
|
4483
4838
|
chunk = await readBounded();
|
|
@@ -4494,10 +4849,10 @@ Missing: ${missing.join("; ")}
|
|
|
4494
4849
|
await reader.cancel();
|
|
4495
4850
|
} catch {
|
|
4496
4851
|
}
|
|
4497
|
-
const secs = Math.round(
|
|
4852
|
+
const secs = Math.round(stallIdleMs / 1e3);
|
|
4498
4853
|
diag.warn("stream_stalled", `${this.providerLabel} stream stalled \u2014 no SSE events for ${secs}s (server hold; aborting call)`, {
|
|
4499
4854
|
provider: this.providerLabel,
|
|
4500
|
-
idle_timeout_ms:
|
|
4855
|
+
idle_timeout_ms: stallIdleMs,
|
|
4501
4856
|
idle_timeout_s: secs
|
|
4502
4857
|
});
|
|
4503
4858
|
return {
|
|
@@ -4510,9 +4865,18 @@ Missing: ${missing.join("; ")}
|
|
|
4510
4865
|
throw err;
|
|
4511
4866
|
}
|
|
4512
4867
|
const { done, value } = chunk;
|
|
4513
|
-
if (done)
|
|
4868
|
+
if (done) {
|
|
4869
|
+
buffer += decoder.decode();
|
|
4514
4870
|
break;
|
|
4515
|
-
|
|
4871
|
+
}
|
|
4872
|
+
receivedBytes += value.byteLength;
|
|
4873
|
+
let decoded = decoder.decode(value, { stream: true });
|
|
4874
|
+
if (decoded) {
|
|
4875
|
+
if (trailingCR && decoded.startsWith("\n"))
|
|
4876
|
+
decoded = decoded.slice(1);
|
|
4877
|
+
trailingCR = decoded.endsWith("\r");
|
|
4878
|
+
buffer += decoded.replace(/\r\n?/g, "\n");
|
|
4879
|
+
}
|
|
4516
4880
|
let boundary = buffer.indexOf("\n\n");
|
|
4517
4881
|
while (boundary >= 0) {
|
|
4518
4882
|
const rawChunk = buffer.slice(0, boundary);
|
|
@@ -4521,27 +4885,46 @@ Missing: ${missing.join("; ")}
|
|
|
4521
4885
|
const payload = rawChunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
|
|
4522
4886
|
if (!payload || payload === "[DONE]")
|
|
4523
4887
|
continue;
|
|
4888
|
+
lastEventAt = Date.now();
|
|
4524
4889
|
let event;
|
|
4525
4890
|
try {
|
|
4526
4891
|
event = JSON.parse(payload);
|
|
4892
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
4893
|
+
malformedEvents++;
|
|
4894
|
+
continue;
|
|
4895
|
+
}
|
|
4527
4896
|
} catch {
|
|
4897
|
+
malformedEvents++;
|
|
4528
4898
|
continue;
|
|
4529
4899
|
}
|
|
4530
4900
|
const type = String(event.type ?? "");
|
|
4531
|
-
if (
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4901
|
+
if (eventTypes.size < 32)
|
|
4902
|
+
eventTypes.add(identifier(type) ?? "unrecognized");
|
|
4903
|
+
const terminal = type === "response.completed" || this.provider === "openrouter" && type === "response.done";
|
|
4904
|
+
if (terminal || type === "response.failed" || type === "response.incomplete" || type === "error") {
|
|
4905
|
+
const response = event.response && typeof event.response === "object" && !Array.isArray(event.response) ? event.response : void 0;
|
|
4906
|
+
const usage = response?.usage;
|
|
4907
|
+
if (usage && typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) && usage.input_tokens >= 0 && typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) && usage.output_tokens >= 0) {
|
|
4908
|
+
responseUsage = {
|
|
4909
|
+
inputTokens: usage.input_tokens,
|
|
4910
|
+
outputTokens: usage.output_tokens,
|
|
4911
|
+
...readResponsesCachedTokens(usage)
|
|
4539
4912
|
};
|
|
4540
|
-
callbacks?.onUsage?.(
|
|
4913
|
+
callbacks?.onUsage?.(responseUsage);
|
|
4541
4914
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4915
|
+
if (!terminal || !response || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed") {
|
|
4916
|
+
const error = response?.error ?? event.error;
|
|
4917
|
+
const incomplete = response?.incomplete_details;
|
|
4918
|
+
streamFailure = {
|
|
4919
|
+
event: type,
|
|
4920
|
+
status: identifier(response?.status),
|
|
4921
|
+
code: identifier(error?.code ?? event.code),
|
|
4922
|
+
errorType: identifier(error?.type),
|
|
4923
|
+
reason: identifier(incomplete?.reason)
|
|
4924
|
+
};
|
|
4925
|
+
break responses;
|
|
4926
|
+
}
|
|
4927
|
+
completedResponse = response;
|
|
4545
4928
|
continue;
|
|
4546
4929
|
}
|
|
4547
4930
|
if (type === "response.output_text.delta" || this.provider === "openrouter" && type === "response.content_part.delta") {
|
|
@@ -4575,33 +4958,33 @@ Missing: ${missing.join("; ")}
|
|
|
4575
4958
|
}
|
|
4576
4959
|
continue;
|
|
4577
4960
|
}
|
|
4578
|
-
if (type === "response.completed" || type === "response.incomplete" || this.provider === "openrouter" && type === "response.done") {
|
|
4579
|
-
const response = event.response;
|
|
4580
|
-
if (response) {
|
|
4581
|
-
if (this.provider === "openrouter" && (openRouterStreamFailed || response.error != null || (type === "response.done" || response.status !== void 0) && response.status !== "completed")) {
|
|
4582
|
-
openRouterStreamFailed = true;
|
|
4583
|
-
continue;
|
|
4584
|
-
}
|
|
4585
|
-
completedResponse = response;
|
|
4586
|
-
const usage2 = response.usage;
|
|
4587
|
-
if (usage2 && this.provider !== "openrouter") {
|
|
4588
|
-
callbacks?.onUsage?.({
|
|
4589
|
-
inputTokens: Number(usage2.input_tokens ?? 0),
|
|
4590
|
-
outputTokens: Number(usage2.output_tokens ?? 0)
|
|
4591
|
-
});
|
|
4592
|
-
}
|
|
4593
|
-
}
|
|
4594
|
-
}
|
|
4595
4961
|
}
|
|
4596
4962
|
}
|
|
4597
4963
|
emitThinking(true);
|
|
4598
|
-
if (!completedResponse ||
|
|
4964
|
+
if (!completedResponse || streamFailure) {
|
|
4965
|
+
if (streamFailure) {
|
|
4966
|
+
void reader.cancel().catch(() => {
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
appendNativeTrace({
|
|
4970
|
+
kind: "native-response-stream-error",
|
|
4971
|
+
provider: this.providerLabel,
|
|
4972
|
+
wireApi: this.wireApi,
|
|
4973
|
+
httpStatus: res.status,
|
|
4974
|
+
eventStreamContentType: /^text\/event-stream(?:\s*;|$)/i.test(res.headers.get("content-type") ?? ""),
|
|
4975
|
+
terminalFailure: streamFailure ?? null,
|
|
4976
|
+
eventTypes: [...eventTypes],
|
|
4977
|
+
receivedBytes,
|
|
4978
|
+
malformedEvents,
|
|
4979
|
+
trailingCharacters: buffer.length,
|
|
4980
|
+
usage: responseUsage ?? null
|
|
4981
|
+
});
|
|
4599
4982
|
return {
|
|
4600
4983
|
content: thinkingText ? [{ type: "text", text: thinkingText }] : [{ type: "text", text: "" }],
|
|
4601
4984
|
stopReason: "error",
|
|
4602
4985
|
durationMs: Date.now() - start,
|
|
4603
|
-
...
|
|
4604
|
-
error: `${this.providerLabel} API error: ${
|
|
4986
|
+
...responseUsage ? { usage: responseUsage } : {},
|
|
4987
|
+
error: `${this.providerLabel} API error: ${streamFailure ? `Responses terminal failure ${JSON.stringify(streamFailure)}` : `stream completed without final response (HTTP ${res.status}; events=${[...eventTypes].join(",") || "none"}; malformed=${malformedEvents}; trailing=${buffer.length})`}`
|
|
4605
4988
|
};
|
|
4606
4989
|
}
|
|
4607
4990
|
appendNativeTrace({
|
|
@@ -4654,21 +5037,10 @@ Missing: ${missing.join("; ")}
|
|
|
4654
5037
|
thinkingText = reasoningSummaries.join("\n");
|
|
4655
5038
|
emitThinking(true);
|
|
4656
5039
|
}
|
|
4657
|
-
const usageRecord = completedResponse.usage;
|
|
4658
|
-
const usage = usageRecord ? {
|
|
4659
|
-
inputTokens: Number(usageRecord.input_tokens ?? 0),
|
|
4660
|
-
outputTokens: Number(usageRecord.output_tokens ?? 0),
|
|
4661
|
-
// Responses `input_tokens` already INCLUDES the cached span (unlike
|
|
4662
|
-
// Anthropic, which subtracts it), so no normalisation is needed —
|
|
4663
|
-
// this is purely so cache behaviour becomes observable. Without it
|
|
4664
|
-
// the Codex cache hit rate is unmeasurable: `prompt-cache.ts`
|
|
4665
|
-
// instruments the Anthropic path only.
|
|
4666
|
-
...readResponsesCachedTokens(usageRecord)
|
|
4667
|
-
} : void 0;
|
|
4668
5040
|
return {
|
|
4669
5041
|
content,
|
|
4670
5042
|
stopReason: content.some((item) => item.type === "tool_use") ? "tool_use" : "end_turn",
|
|
4671
|
-
usage,
|
|
5043
|
+
usage: responseUsage,
|
|
4672
5044
|
durationMs: Date.now() - start,
|
|
4673
5045
|
// `outputItems` is the complete, correctly-ordered response array —
|
|
4674
5046
|
// reasoning items with their `encrypted_content` still attached, each
|
|
@@ -4724,5 +5096,6 @@ export {
|
|
|
4724
5096
|
OperatorAbortError,
|
|
4725
5097
|
parseUsageLimitReached,
|
|
4726
5098
|
LOOP_SERVER_COMPACTION_TOKENS,
|
|
5099
|
+
getChatGptCodexAccessToken,
|
|
4727
5100
|
LlmApiRuntime
|
|
4728
5101
|
};
|